### Verify Sealed Monad Installation Source: https://github.com/theiterators/sealed-monad/blob/master/docs/installation.md A functional test snippet demonstrating the use of Sealed Monad for error handling with for-comprehensions. It defines a sealed trait and uses the library to transform an Option into a custom response type. ```scala import pl.iterators.sealedmonad.syntax._ import cats.Id sealed trait Response case class Success(value: String) extends Response case object NotFound extends Response def test(input: Option[String]): Id[Response] = { (for { value <- input.valueOr[Response](NotFound) } yield Success(value)).run } val result1 = test(Some("Hello")) val result2 = test(None) ``` -------------------------------- ### Import Sealed Monad Syntax Source: https://github.com/theiterators/sealed-monad/blob/master/docs/installation.md Standard and recommended import statements to access Sealed Monad functionality and syntax extensions in Scala source files. ```scala import pl.iterators.sealedmonad._ import pl.iterators.sealedmonad.syntax._ ``` -------------------------------- ### Complete Login Flow Example with Sealed Monad Source: https://context7.com/theiterators/sealed-monad/llms.txt A comprehensive example showcasing a realistic user authentication flow using the Sealed Monad. It includes multiple validation steps, data retrieval, and response handling, culminating in a `Future[LoginResponse]`. ```scala import pl.iterators.sealedmonad.syntax._ import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global // Domain models case class User(id: Long, email: String, archived: Boolean) case class AuthMethod(userId: Long, provider: String, valid: Boolean) // Response ADT sealed trait LoginResponse object LoginResponse { final case class LoggedIn(token: String) extends LoginResponse case object InvalidCredentials extends LoginResponse case object Deleted extends LoginResponse case object ProviderAuthFailed extends LoginResponse } // Repository functions def findUser(email: String): Future[Option[User]] = Future.successful(Some(User(1L, email, archived = false))) def findAuthMethod(userId: Long, provider: String): Future[Option[AuthMethod]] = Future.successful(Some(AuthMethod(userId, provider, valid = true))) def checkAuthMethod(am: AuthMethod): Boolean = am.valid def issueTokenFor(user: User): String = s"jwt-${user.id}-${System.currentTimeMillis}" // Login implementation with Sealed Monad def login(email: String): Future[LoginResponse] = { (for { user <- findUser(email) .valueOr[LoginResponse](LoginResponse.InvalidCredentials) .ensure(!_.archived, LoginResponse.Deleted) authMethod <- findAuthMethod(user.id, "email") .valueOr[LoginResponse](LoginResponse.ProviderAuthFailed) .ensure(checkAuthMethod, LoginResponse.InvalidCredentials) } yield LoginResponse.LoggedIn(issueTokenFor(user))).run } // Usage val result = login("user@example.com") // Result: LoggedIn("jwt-1-1234567890") ``` -------------------------------- ### Configure Build Dependencies for Sealed Monad Source: https://github.com/theiterators/sealed-monad/blob/master/docs/installation.md Instructions for adding the Sealed Monad library to common JVM build systems. Users must select the appropriate configuration based on their project's build tool (SBT, Mill, Maven, or Gradle). ```scala libraryDependencies += "pl.iterators" %% "sealed-monad" % "2.0.1" ``` ```scala import $ivy.`pl.iterators::sealed-monad:2.0.1` object myModule extends ScalaModule { def scalaVersion = "2.13.12" def ivyDeps = Agg( ivy"pl.iterators::sealed-monad:2.0.1" ) } ``` ```xml pl.iterators sealed_monad_${scala.binary.version} 1.3 ``` ```kotlin val scalaBinaryVersion = "2.13" dependencies { implementation("pl.iterators:sealed-monad_${scalaBinaryVersion}:1.3") } ``` -------------------------------- ### Migrate Email Confirmation Logic to Sealed Monad Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md Demonstrates the refactoring of a Future-based email confirmation function. It replaces EitherT transformers with the sealed-monad syntax for cleaner for-comprehension structures. ```scala def confirmEmail( token: String, findAuthMethod: String => Future[Option[AuthMethod]], findUser: Long => Future[Option[User]], upsertAuthMethod: AuthMethod => Future[Int], issueTokenFor: User => String, confirmMethod: AuthMethod => AuthMethod ): Future[ConfirmResponse] = { import scala.concurrent.ExecutionContext.Implicits.global import cats.implicits._ val userT = for { method <- EitherT.fromOptionF( findAuthMethod(token), ifNone = ConfirmResponse.MethodNotFound ) user <- EitherT.fromOptionF( findUser(method.userId), ifNone = ConfirmResponse.UserNotFound ) } yield (method, user) userT.semiflatMap { case (method, user) => upsertAuthMethod(confirmMethod(method)) .map(_ => ConfirmResponse.Confirmed(issueTokenFor(user))) }.merge } ``` ```scala def confirmEmail( token: String, findAuthMethod: String => Future[Option[AuthMethod]], findUser: Long => Future[Option[User]], upsertAuthMethod: AuthMethod => Future[Int], issueTokenFor: User => String, confirmMethod: AuthMethod => AuthMethod ): Future[ConfirmResponse] = { import scala.concurrent.ExecutionContext.Implicits.global import pl.iterators.sealedmonad.syntax._ (for { method <- findAuthMethod(token) .valueOr[ConfirmResponse](ConfirmResponse.MethodNotFound) user <- findUser(method.userId) .valueOr[ConfirmResponse](ConfirmResponse.UserNotFound) _ <- upsertAuthMethod(confirmMethod(method)).seal } yield ConfirmResponse.Confirmed(issueTokenFor(user))).run } ``` -------------------------------- ### Install Sealed Monad via sbt Source: https://context7.com/theiterators/sealed-monad/llms.txt Add the library dependency to your build.sbt file and import the syntax package to enable Sealed Monad features. ```scala libraryDependencies += "pl.iterators" %% "sealed-monad" % "2.0.1" import pl.iterators.sealedmonad.syntax._ ``` -------------------------------- ### Integrate Option-Returning Functions with Sealed Monad in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md This Scala example illustrates how to integrate functions that return Option with the Sealed Monad. The `valueOr` method is used to convert a potential None result into a specific error case within the Sealed Monad. ```scala // When calling external code that returns Option def callLegacyCode(id: String): Sealed[F, User, UserError] = legacyService.findUserById(id).valueOr(UserError.NotFound) ``` -------------------------------- ### Handle Dependency Conflicts Source: https://github.com/theiterators/sealed-monad/blob/master/docs/installation.md Snippet for overriding dependency versions in SBT to resolve potential conflicts with cats-core or other transitive dependencies. ```scala dependencyOverrides += "org.typelevel" %% "cats-core" % "2.10.0" ``` -------------------------------- ### Scala: Migrate Pattern Matching to Sealed Monad Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md Demonstrates refactoring Scala code from nested Option/Either pattern matching to a more concise structure using Sealed Monad operators like `valueOr` and `seal` within a for-comprehension. This approach simplifies error handling and improves readability. ```scala sealed trait ConfirmResponse object ConfirmResponse { case object MethodNotFound extends ConfirmResponse case object UserNotFound extends ConfirmResponse final case class Confirmed(token: String) extends ConfirmResponse } import pl.iterators.sealedmonad.syntax._ import scala.concurrent.Future // Before: def confirmEmail( token: String, findAuthMethod: String => Future[Option[AuthMethod]], findUser: Long => Future[Option[User]], upsertAuthMethod: AuthMethod => Future[Int], issueTokenFor: User => String, confirmMethod: AuthMethod => AuthMethod ): Future[ConfirmResponse] = { import scala.concurrent.ExecutionContext.Implicits.global findAuthMethod(token).flatMap { case None => Future.successful(ConfirmResponse.MethodNotFound) case Some(method) => findUser(method.userId).flatMap { case None => Future.successful(ConfirmResponse.UserNotFound) case Some(user) => upsertAuthMethod(confirmMethod(method)).map { _ => ConfirmResponse.Confirmed(issueTokenFor(user)) } } } } // After: def confirmEmail( token: String, findAuthMethod: String => Future[Option[AuthMethod]], findUser: Long => Future[Option[User]], upsertAuthMethod: AuthMethod => Future[Int], issueTokenFor: User => String, confirmMethod: AuthMethod => AuthMethod ): Future[ConfirmResponse] = { import scala.concurrent.ExecutionContext.Implicits.global (for { // Find auth method or return MethodNotFound method <- findAuthMethod(token) .valueOr[ConfirmResponse](ConfirmResponse.MethodNotFound) // Find user or return UserNotFound user <- findUser(method.userId) .valueOr[ConfirmResponse](ConfirmResponse.UserNotFound) // Update auth method _ <- upsertAuthMethod(confirmMethod(method)).seal } yield ConfirmResponse.Confirmed(issueTokenFor(user))).run } ``` -------------------------------- ### Refactor OrderService with Sealed Monad Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md Demonstrates the migration of a nested Future-based order processing service to a cleaner implementation using Sealed Monad syntax. The refactored version uses a for-comprehension to handle validation and service calls, replacing deeply nested pattern matching. ```scala class OrderService( repository: OrderRepository, paymentService: PaymentService, notificationService: NotificationService ) { import scala.concurrent.ExecutionContext.Implicits.global def placeOrder(userId: String, items: List[OrderItem]): Future[Either[String, Order]] = { if (items.isEmpty) { return Future.successful(Left("Order must contain at least one item")) } repository.findActiveCartByUser(userId).flatMap { case None => Future.successful(Left("No active cart found")) case Some(cart) => if (cart.items.isEmpty) { Future.successful(Left("Cart is empty")) } else { val order = Order( id = generateOrderId(), userId = userId, items = cart.items, status = "pending", createdAt = Instant.now() ) repository.createOrder(order).flatMap { createdOrder => paymentService.processPayment(userId, calculateTotal(cart.items)).flatMap { case Right(paymentId) => val finalOrder = createdOrder.copy( paymentId = Some(paymentId), status = "paid" ) repository.updateOrder(finalOrder).flatMap { updatedOrder => notificationService.sendOrderConfirmation(updatedOrder) .map(_ => Right(updatedOrder)) } case Left(error) => Future.successful(Left(s"Payment failed: $error")) } } } } } } ``` ```scala sealed trait OrderResult object OrderResult { case class Success(order: Order) extends OrderResult case object EmptyItems extends OrderResult case object NoActiveCart extends OrderResult case object EmptyCart extends OrderResult case class PaymentFailed(reason: String) extends OrderResult } class OrderService(...) { import pl.iterators.sealedmonad.syntax._ def placeOrder(userId: String, items: List[OrderItem]): Future[OrderResult] = { (for { _ <- items.pure[Future].ensure(_.nonEmpty, OrderResult.EmptyItems) cart <- repository.findActiveCartByUser(userId).valueOr[OrderResult](OrderResult.NoActiveCart) _ <- cart.pure[Future].ensure(_.items.nonEmpty, OrderResult.EmptyCart) order = Order(...) createdOrder <- repository.createOrder(order).seal paymentId <- paymentService.processPayment(userId, calculateTotal(cart.items)).attempt { case Right(id) => Right(id) case Left(error) => Left(OrderResult.PaymentFailed(error)) } finalOrder = createdOrder.copy(paymentId = Some(paymentId), status = "paid") updatedOrder <- repository.updateOrder(finalOrder).seal _ <- notificationService.sendOrderConfirmation(updatedOrder).seal } yield OrderResult.Success(updatedOrder)).run } } ``` -------------------------------- ### Resolving Sealed Monad Compilation Errors Source: https://github.com/theiterators/sealed-monad/blob/master/docs/faq.md Provides solutions for the 'run' method type constraint error, where the success type must be a subtype of the error ADT. Includes examples of mapping values and using pattern matching. ```scala // 1. Make your success type extend your error type sealed trait Response case class Success(value: Int) extends Response case object NotFound extends Response val result: Future[Response] = sealedValue.run // 2. Map your intermediate value to a result type val result: Future[Response] = sealedInt.map(Success).run // 3. Use pattern matching after the computation val either: Future[Either[Error, Int]] = sealedInt.map(Right(_)).getOrElse(Left(Error)) either.map { case Right(value) => handleSuccess(value) case Left(error) => handleError(error) } ``` -------------------------------- ### Wrapper for Legacy Option Function with Sealed Monad in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md This Scala code snippet shows how to create a wrapper for a legacy function that returns Option. The wrapper converts the Option result into a UserResponse ADT, making it compatible with the Sealed Monad for unified error handling. ```scala // Original method def findUser(id: String): Future[Option[User]] = ??? // Wrapper for use with Sealed Monad def findUserOrError(id: String): Future[UserResponse] = findUser(id).map(_.fold[UserResponse](UserResponse.NotFound)(UserResponse.Found)) ``` -------------------------------- ### Scala: Migrate Try/Either/Exception Handling to Sealed Monad Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md Illustrates the transformation of Scala code that uses `Try`, `Either`, or direct exception handling into a `Future`-based flow managed by Sealed Monad. This involves defining a result ADT and using operators like `attempt` and `ensure` within a for-comprehension to linearize the control flow. ```scala import scala.concurrent.Future import scala.util.{Success, Failure} // Assume OrderProcessingResult is a sealed trait ADT // sealed trait OrderProcessingResult // object OrderProcessingResult { // case object NotFound extends OrderProcessingResult // case object Expired extends OrderProcessingResult // case object InsufficientFunds extends OrderProcessingResult // case object PaymentFailed extends OrderProcessingResult // case object ShippingFailed extends OrderProcessingResult // final case class Completed(trackingId: String) extends OrderProcessingResult // } // Before: def processOrder( orderId: String, orderRepository: OrderRepository, paymentService: PaymentService, shippingService: ShippingService ): Future[OrderProcessingResult] = { import scala.concurrent.ExecutionContext.Implicits.global orderRepository.findById(orderId).flatMap { case None => Future.successful(OrderProcessingResult.NotFound) case Some(order) => if (order.isExpired) { Future.successful(OrderProcessingResult.Expired) } else { paymentService.processPayment(order.id, order.amount).flatMap { case Success(paymentId) => shippingService.arrangeShipping(order).transform { case Success(trackingId) => Success(OrderProcessingResult.Completed(trackingId)) case Failure(_) => Success(OrderProcessingResult.ShippingFailed) } case Failure(e: InsufficientFundsException) => Future.successful(OrderProcessingResult.InsufficientFunds) case Failure(_) => Future.successful(OrderProcessingResult.PaymentFailed) } } } } // After (Conceptual using Sealed Monad - requires specific operators like .attempt, .ensure, .valueOr, .seal): // import pl.iterators.sealedmonad.syntax._ // def processOrder( // orderId: String, // orderRepository: OrderRepository, // paymentService: PaymentService, // shippingService: ShippingService // ): Future[OrderProcessingResult] = { // import scala.concurrent.ExecutionContext.Implicits.global // // (for { // order <- orderRepository.findById(orderId).valueOr(OrderProcessingResult.NotFound) // _ <- if (order.isExpired) OrderProcessingResult.Expired.asFuture else Future.successful(()) // paymentId <- paymentService.processPayment(order.id, order.amount).attempt // trackingId <- shippingService.arrangeShipping(order).attempt // } yield OrderProcessingResult.Completed(trackingId)).run // // Note: This is a simplified representation. Actual implementation would need careful mapping of exceptions and results to the ADT. } ``` -------------------------------- ### Fluent Validation and Error Handling Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md Examples of using Sealed Monad operators like valueOr, ensure, and attempt to chain business validations and handle errors cleanly. ```scala // Extract Option or return error userRepository.findById(userId) .valueOr[UserResponse](UserResponse.NotFound) // Chain multiple validations userRepository.findById(userId) .valueOr[UserResponse](UserResponse.NotFound) .ensure(!_.archived, UserResponse.AccountDeleted) .ensure(_.isActive, UserResponse.AccountInactive) // Complex transformation with attempt validateAddress(address).attempt { case Right(validatedAddress) => Right(validatedAddress) case Left(AddressError.InvalidZipCode) => Left(UserResponse.InvalidZipCode) case Left(AddressError.UnknownCity) => Left(UserResponse.UnknownCity) case Left(_) => Left(UserResponse.InvalidAddress) } ``` -------------------------------- ### Convert EitherT to Sealed Monad in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md This Scala function demonstrates how to convert an EitherT instance to a Sealed Monad. It leverages the `value` and `rethrow` methods to transform the error channel of EitherT into the Sealed Monad's structure. ```scala import cats.data.EitherT import cats.implicits._ import pl.iterators.sealedmonad.Sealed import pl.iterators.sealedmonad.syntax._ // Convert from EitherT to Sealed def fromEitherT[F[_], A, B](eitherT: EitherT[F, B, A])(implicit M: Monad[F]): Sealed[F, A, B] = Sealed(eitherT.value).rethrow ``` -------------------------------- ### Process Order with Sealed Monad in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md This Scala function demonstrates processing an order using the Sealed Monad for comprehensive error management. It handles various stages like finding the order, checking expiration, processing payment, and arranging shipping, returning a detailed OrderProcessingResult. ```scala def processOrder( orderId: String, orderRepository: OrderRepository, paymentService: PaymentService, shippingService: ShippingService ): Future[OrderProcessingResult] = { import scala.concurrent.ExecutionContext.Implicits.global import pl.iterators.sealedmonad.syntax._ (for { // Find order or return NotFound order <- orderRepository.findById(orderId) .valueOr[OrderProcessingResult](OrderProcessingResult.NotFound) // Check if order is expired _ <- order.pure[Future] .ensure(!_.isExpired, OrderProcessingResult.Expired) // Process payment paymentId <- paymentService.processPayment(order.id, order.amount) .attempt { case Success(id) => Right(id) case Failure(e: InsufficientFundsException) => Left(OrderProcessingResult.InsufficientFunds) case Failure(_) => Left(OrderProcessingResult.PaymentFailed) } // Arrange shipping trackingId <- shippingService.arrangeShipping(order) .attempt { case Success(id) => Right(id) case Failure(_) => Left(OrderProcessingResult.ShippingFailed) } } yield OrderProcessingResult.Completed(trackingId)).run } ``` -------------------------------- ### Model Domain-Specific Edge Cases in Payment Results (Scala) Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md This example demonstrates modeling domain-specific edge cases within a `PaymentResult` sealed trait. It includes outcomes like `InsufficientFunds`, `CardDeclined`, and a domain-specific `FraudDetected`, alongside general processing errors. ```scala sealed trait PaymentResult object PaymentResult { final case class Success(transactionId: String) extends PaymentResult case object InsufficientFunds extends PaymentResult case object CardDeclined extends PaymentResult case object PaymentMethodExpired extends PaymentResult case object FraudDetected extends PaymentResult // Domain-specific case case object ProcessorUnavailable extends PaymentResult } ``` -------------------------------- ### Convert Sealed Monad to EitherT in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/migration-guide.md This Scala function shows how to convert a Sealed Monad instance back to an EitherT. It uses the `either.run` method to extract the underlying F[Either[B, A]] structure, which is then used to construct the EitherT. ```scala import cats.data.EitherT import cats.implicits._ import pl.iterators.sealedmonad.Sealed import pl.iterators.sealedmonad.syntax._ // Convert from Sealed to EitherT def toEitherT[F[_]: Monad, A, B](sealed: Sealed[F, A, B]): EitherT[F, B, A] = EitherT(sealed.either.run) ``` -------------------------------- ### Define Service Method Signature Source: https://github.com/theiterators/sealed-monad/blob/master/docs/motivations.md An example of a service method signature designed to return a unified LoginResponse ADT, avoiding arbitrary error/success distinctions. ```scala def login(email: String, findUser: String => Future[Option[User]], findAuthMethod: (Long, Provider) => Future[Option[AuthMethod]], issueTokenFor: User => String, checkAuthMethod: AuthMethod => Boolean): Future[LoginResponse] = ??? ``` -------------------------------- ### Creating Sealed Instances Source: https://github.com/theiterators/sealed-monad/blob/master/docs/api-reference.md Provides methods for creating `Sealed` instances from various sources like effects, pure values, ADT values, Options, and Eithers. ```APIDOC ## Creating Sealed Instances ### From Effects ```scala // From F[A] def seal[ADT]: Sealed[F, A, ADT] // Example: val sealedValue: Sealed[IO, Int, String] = IO.pure(42).seal[String] // From pure values def liftSealed[F[_], ADT]: Sealed[F, A, ADT] // Example: val sealedValue: Sealed[IO, Int, String] = 42.liftSealed[IO, String] // From ADT values def seal[F[_]]: Sealed[F, Nothing, A] // Example: val sealedError: Sealed[IO, Nothing, String] = "error".seal[IO] ``` ### From Options ```scala // valueOr - extract value from Option or return ADT def valueOr[ADT](orElse: => ADT): Sealed[F, A, ADT] // Example: val maybeUser: IO[Option[User]] = userRepository.findById(userId) val sealedUser: Sealed[IO, User, MyError] = maybeUser.valueOr(MyError.UserNotFound) // valueOrF - extract value from Option or return effectful ADT def valueOrF[ADT](orElse: => F[ADT]): Sealed[F, A, ADT] // Example: val sealedUser: Sealed[IO, User, MyError] = maybeUser.valueOrF(Logger[IO].error("User not found") *> IO.pure(MyError.UserNotFound)) ``` ### From Either ```scala // Convert F[Either[A, B]] to Sealed[F, B, A] def fromEither: Sealed[F, B, A] // Example: val result: IO[Either[String, Int]] = validateInput(data) val sealedResult: Sealed[IO, Int, String] = result.fromEither // handleError - convert F[Either[A, B]] to Sealed[F, B, ADT] def handleError[ADT](f: A => ADT): Sealed[F, B, ADT] // Example: val sealedResult: Sealed[IO, Int, MyError] = result.handleError(msg => MyError.ValidationFailed(msg)) ``` ``` -------------------------------- ### Define ADT for User Responses Source: https://github.com/theiterators/sealed-monad/blob/master/docs/api-reference.md Example of defining a sealed trait hierarchy for representing different outcomes of a user-related operation. This pattern is fundamental to Sealed Monad's design, where success and error cases are part of the same ADT. ```scala sealed trait UserResponse case class Success(user: User) extends UserResponse case object NotFound extends UserResponse case object Unauthorized extends UserResponse ``` -------------------------------- ### Execute Sealed Computation to Future[LoginResponse] Source: https://context7.com/theiterators/sealed-monad/llms.txt Demonstrates how to use the .run method to execute a Sealed computation, transforming it into a `Future[LoginResponse]`. This involves defining case classes and traits for responses, and using helper functions for data retrieval and token issuance. ```scala import pl.iterators.sealedmonad.syntax._ import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global sealed trait LoginResponse case class LoggedIn(token: String) extends LoginResponse case object InvalidCredentials extends LoginResponse case object Deleted extends LoginResponse case class User(id: Long, archived: Boolean) def findUser(email: String): Future[Option[User]] = Future.successful(Some(User(1L, archived = false))) def issueToken(userId: Long): String = s"token-$userId" // Without .run - returns Sealed[Future, LoginResponse, LoginResponse] val sealed = for { user <- findUser("test@example.com") .valueOr[LoginResponse](InvalidCredentials) .ensure(!_.archived, Deleted) } yield LoggedIn(issueToken(user.id)) // With .run - returns Future[LoginResponse] val result: Future[LoginResponse] = sealed.run // Result: LoggedIn("token-1") ``` -------------------------------- ### Create Sealed Instances from Options (Scala) Source: https://github.com/theiterators/sealed-monad/blob/master/docs/api-reference.md Shows how to convert `Option` types into `Sealed` instances, providing default values or effectful fallbacks for missing values. This is useful for handling optional data gracefully. ```scala // valueOr - extract value from Option or return ADT val sealedUser: Sealed[IO, User, MyError] = maybeUser.valueOr(MyError.UserNotFound) // valueOrF - extract value from Option or return effectful ADT val sealedUser: Sealed[IO, User, MyError] = maybeUser.valueOrF(Logger[IO].error("User not found") *> IO.pure(MyError.UserNotFound)) ``` -------------------------------- ### Login Logic with ZIO Source: https://github.com/theiterators/sealed-monad/blob/master/docs/comparison.md Implements login logic using ZIO, leveraging its effect system for robust error handling. This approach involves finding the user, checking for archived status, retrieving the authentication method, and verifying its validity before issuing a token. It uses ZIO's `Task`, `filterOrElseWith`, and `catchAll` for managing effects and errors. Dependencies include `zio._`. ```scala import zio._ def login( email: String, findUser: String => Task[Option[User]], findAuthMethod: (Long, String) => Task[Option[AuthMethod]], checkAuthMethod: AuthMethod => Boolean, issueTokenFor: User => String ): Task[LoginResponse] = { // Find user ZIO.fromOption(findUser(email).orDie) .mapError(_ => LoginResponse.InvalidCredentials) // Check if user is archived .filterOrElseWith( user => !user.archived, _ => ZIO.succeed(LoginResponse.Deleted) ) // Get auth method .flatMap(user => ZIO.fromOption(findAuthMethod(user.id, "email").orDie) .mapError(_ => LoginResponse.ProviderAuthFailed) // Check auth method validity .filterOrElseWith( authMethod => checkAuthMethod(authMethod), _ => ZIO.succeed(LoginResponse.InvalidCredentials) ) // Create success response .map(_ => LoginResponse.LoggedIn(issueTokenFor(user))) ).catchAll(ZIO.succeed(_)) } ``` -------------------------------- ### Login Function without Sealed Monad (Scala) Source: https://github.com/theiterators/sealed-monad/blob/master/docs/intro.md Demonstrates a traditional approach to handling login logic using nested `flatMap` and pattern matching in Scala. This method can lead to deeply nested and less readable code, especially with multiple error conditions. ```scala def login(email: String, ...): Future[LoginResponse] = { findUser(email).flatMap { case None => Future.successful(LoginResponse.InvalidCredentials) case Some(user) if user.archived => Future.successful(LoginResponse.Deleted) case Some(user) => findAuthMethod(user.id, Provider.EmailPass).map { case None => LoginResponse.ProviderAuthFailed case Some(am) if !checkAuthMethod(am) => LoginResponse.InvalidCredentials case Some(_) => LoginResponse.LoggedIn(issueTokenFor(user)) } } } ``` -------------------------------- ### Performing Side Effects with flatTap in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md The flatTap operator enables performing side effects, such as recording access events, without altering the primary computation's result. This example records user access after a successful login. ```scala // Perform side effects without affecting the main computation user.flatTap(u => auditService.recordAccess(u.id, AccessType.Login) ) ``` -------------------------------- ### Syntax Extensions Import Source: https://github.com/theiterators/sealed-monad/blob/master/docs/api-reference.md Enables the extension methods provided by the Sealed Monad library for various types. ```scala import pl.iterators.sealedmonad.syntax._ ``` -------------------------------- ### Create Sealed Instances from Effects (Scala) Source: https://github.com/theiterators/sealed-monad/blob/master/docs/api-reference.md Demonstrates how to create `Sealed` instances from effectful computations like `IO`. It shows sealing from `F[A]` and lifting pure values into a `Sealed` context. ```scala // From F[A] val sealedValue: Sealed[IO, Int, String] = IO.pure(42).seal[String] // From pure values val sealedValue: Sealed[IO, Int, String] = 42.liftSealed[IO, String] ``` -------------------------------- ### Logging Errors with valueOrF in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md The valueOrF operator extends the valueOr pattern with effectful error handling. When a job cannot be found, this example logs the error before returning the failure case, improving observability while preserving the typed error channel. ```scala private def findJob(jobName: String): Step[Job] = jobService.findJob(jobName) .valueOrF(Logger[IO].error(s"Unable to find job $jobName").as(JobResult.JobNotFound)) ``` -------------------------------- ### Logging Different Outcomes with inspect in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md The inspect operator allows for logging different outcomes of a computation distinctly. This example demonstrates logging informational messages for successful validations, warnings for invalid email formats, and errors for other validation failures. ```scala // Log different outcomes differently userValidation.inspect { case Right(user) => logger.info(s"User validated: ${user.email}") case Left(error: ValidationError.InvalidEmail) => logger.warn(s"Invalid email format: ${error.email}") case Left(error) => logger.error(s"Validation failed: $error") } ``` -------------------------------- ### Implement Authentication with Pattern Matching Source: https://github.com/theiterators/sealed-monad/blob/master/docs/comparison.md Demonstrates a standard approach to authentication using nested pattern matching on Future results. This approach is highly explicit but prone to deep nesting and reduced readability. ```scala def login( email: String, findUser: String => Future[Option[User]], findAuthMethod: (Long, String) => Future[Option[AuthMethod]], checkAuthMethod: AuthMethod => Boolean, issueTokenFor: User => String ): Future[LoginResponse] = { import scala.concurrent.ExecutionContext.Implicits.global findUser(email).flatMap { case None => Future.successful(LoginResponse.InvalidCredentials) case Some(user) if user.archived => Future.successful(LoginResponse.Deleted) case Some(user) => findAuthMethod(user.id, "email").flatMap { case None => Future.successful(LoginResponse.ProviderAuthFailed) case Some(authMethod) if !checkAuthMethod(authMethod) => Future.successful(LoginResponse.InvalidCredentials) case Some(_) => Future.successful(LoginResponse.LoggedIn(issueTokenFor(user))) } } } ``` -------------------------------- ### Execute Sealed Monad computations with .run Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md Demonstrates the requirement to invoke the .run method on a for-comprehension block to trigger the actual execution of the Sealed Monad computation. ```scala def processOrder(id: String): IO[OrderResponse] = { (for { order <- orderRepository.findById(id).valueOr(OrderResponse.NotFound) } yield OrderResponse.Success(order.id)).run } ``` -------------------------------- ### tap - Execute Side Effect in Cats Id Source: https://context7.com/theiterators/sealed-monad/llms.txt Executes a side effect on the intermediate value without altering the computation's result, useful for debugging and logging. This example uses `cats.Id` for synchronous, pure computations. Dependencies include `pl.iterators.sealedmonad.syntax._` and `cats.Id`. ```scala import pl.iterators.sealedmonad.syntax._ import cats.Id sealed trait ProcessResult case class Success(value: Int) extends ProcessResult case object Failed extends ProcessResult val result: Id[ProcessResult] = (for { value <- Id(Option(42)).valueOr[ProcessResult](Failed) .tap(v => println(s"Processing value: $v")) // Prints: Processing value: 42 } yield Success(value)).run // Result: Success(42) ``` -------------------------------- ### Effectful Validation with ensureF in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md The ensureF operator combines validation with effectful error handling. This example ensures query results are non-empty, logging an error and returning a domain error if they are not. It's ideal for validating results and providing failure context. ```scala private def listJobs(filters: Seq[JobFilter]): JobStep[Seq[Job]] = jobRepository .list(filters) .ensureF( _.nonEmpty, Logger[IO] .error("No matching job found for request") .as(JobResult.NotFound) ) ``` -------------------------------- ### Integrate Sealed Monad with legacy code Source: https://github.com/theiterators/sealed-monad/blob/master/docs/faq.md Provides patterns for wrapping existing Option, Either, or exception-throwing code into the Sealed Monad structure. ```scala // Wrap Option-returning functions legacyService.findUserById(id) // Future[Option[User]] .valueOr(UserError.NotFound) // Sealed[Future, User, UserError] // Wrap Either-returning functions legacyService.validateInput(data) // Future[Either[String, ValidatedData]] .fromEither // Sealed[Future, ValidatedData, String] // Use attempt for exception-handling code IO.delay(legacyService.riskyOperation()) // IO[Result] .attempt { case Right(result) => Right(result) case Left(ex: NotFoundException) => Left(Error.NotFound) case Left(ex) => Left(Error.Unknown(ex.getMessage)) } ``` -------------------------------- ### inspect - Debug Current State in Cats Id Source: https://context7.com/theiterators/sealed-monad/llms.txt Executes a fire-and-forget side effect on the current state (intermediate or final) for debugging purposes. It can inspect either a successful value or an error. This example uses `cats.Id` for synchronous operations. Dependencies include `pl.iterators.sealedmonad.syntax._` and `cats.Id`. ```scala import pl.iterators.sealedmonad.syntax._ import cats.Id sealed trait DebugResult case class Value(i: Int) extends DebugResult case object NotFound extends DebugResult // With a value present val withValue: Id[DebugResult] = (for { x <- Id(Option(42)).valueOr[DebugResult](NotFound) .inspect { case Right(v) => println(s"Has value: $v") case Left(err) => println(s"Has error: $err") } } yield Value(x)).run // Prints: "Has value: 42" // Result: Value(42) // With no value val withoutValue: Id[DebugResult] = (for { x <- Id(Option.empty[Int]).valueOr[DebugResult](NotFound) .inspect { case Right(v) => println(s"Has value: $v") case Left(err) => println(s"Has error: $err") } } yield Value(x)).run // Prints: "Has error: NotFound" // Result: NotFound ``` -------------------------------- ### Perform multi-step validation with Sealed Monad Source: https://github.com/theiterators/sealed-monad/blob/master/docs/faq.md Demonstrates how to chain multiple validation steps using a for-comprehension. The computation short-circuits upon the first failure, returning the specific validation result. ```scala import pl.iterators.sealedmonad.syntax._ import cats.effect.IO def validateOrder(order: Order): IO[OrderValidationResult] = { (for { // Validate order has items _ <- IO.pure(order.items.nonEmpty) .ensure(identity, OrderValidationResult.EmptyOrder) // Validate all items are in stock stockCheck <- inventoryService.checkStock(order.items).seal _ <- IO.pure(stockCheck.allInStock) .ensure(identity, OrderValidationResult.OutOfStock(stockCheck.outOfStockItems)) // Validate payment information _ <- validatePaymentInfo(order.payment) .valueOr(OrderValidationResult.InvalidPayment) // Validate shipping address _ <- validateShippingAddress(order.shippingAddress) .valueOr(OrderValidationResult.InvalidShippingAddress) } yield OrderValidationResult.Valid).run } ``` -------------------------------- ### Create Sealed Instances from Either (Scala) Source: https://github.com/theiterators/sealed-monad/blob/master/docs/api-reference.md Demonstrates converting `Either` types into `Sealed` instances, mapping error and success channels to the ADT. This allows for unified error handling. ```scala // Convert F[Either[A, B]] to Sealed[F, B, A] val sealedResult: Sealed[IO, Int, String] = result.fromEither // handleError - convert F[Either[A, B]] to Sealed[F, B, ADT] val sealedResult: Sealed[IO, Int, MyError] = result.handleError(msg => MyError.ValidationFailed(msg)) ``` -------------------------------- ### Debugging Sealed Monad with Inspect and Tap Source: https://github.com/theiterators/sealed-monad/blob/master/docs/faq.md Shows how to use the inspect and tap operators to log side effects and state transitions within a monadic flow without disrupting the computation. ```scala import pl.iterators.sealedmonad.syntax._ import cats.effect.IO def processOrder(orderId: String): IO[OrderResponse] = { (for { order <- orderRepository.findById(orderId) .valueOr(OrderResponse.NotFound) .inspect { case Right(o) => println(s"Found order: $orderId") case Left(OrderResponse.NotFound) => println(s"Order not found: $orderId") } result <- processOrderItems(order.items) .tap(r => println(s"Processed ${r.size} items")) } yield OrderResponse.Success(order.id)).run } ``` -------------------------------- ### Testing Sealed Monad with ScalaTest Source: https://github.com/theiterators/sealed-monad/blob/master/docs/faq.md Demonstrates how to test Sealed Monad logic using ScalaTest and Cats Effect. It utilizes unsafeRunSync to execute effectful code and verifies outcomes against expected ADT responses. ```scala import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import cats.effect.unsafe.implicits.global class UserServiceSpec extends AnyFlatSpec with Matchers { "UserService.findUser" should "return Success when user exists" in { val service = new UserService( repository = mockRepository(existingUserId = Some("user-123")) ) val result = service.findUser("user-123").unsafeRunSync() result shouldBe UserResponse.Success(User("user-123", "test@example.com")) } it should "return NotFound when user doesn't exist" in { val service = new UserService( repository = mockRepository(existingUserId = None) ) val result = service.findUser("invalid").unsafeRunSync() result shouldBe UserResponse.NotFound } private def mockRepository(existingUserId: Option[String]): UserRepository = new UserRepository { def findById(id: String): IO[Option[User]] = IO.pure( if (existingUserId.contains(id)) Some(User(id, "test@example.com")) else None ) } } ``` -------------------------------- ### semiflatMap - Transform with Effectful Function in Cats Effect Source: https://context7.com/theiterators/sealed-monad/llms.txt Transforms an intermediate value `A` to `B` using an effectful function `A => F[B]`, useful for asynchronous transformations. This example uses Cats Effect's `IO` for managing side effects and computations. Dependencies include `pl.iterators.sealedmonad.syntax._` and `cats.effect.IO`. ```scala import pl.iterators.sealedmonad.syntax._ import cats.effect.IO sealed trait EnrichmentResult case class EnrichedUser(name: String, preferences: List[String]) extends EnrichmentResult case object NotFound extends EnrichmentResult case class User(id: Long, name: String) def fetchPreferences(userId: Long): IO[List[String]] = IO.pure(List("dark-mode", "notifications")) val user = User(1L, "Alice") val result: IO[EnrichmentResult] = (for { enriched <- IO.pure(Option(user)).valueOr[EnrichmentResult](NotFound) .semiflatMap(u => fetchPreferences(u.id).map(prefs => EnrichedUser(u.name, prefs))) } yield enriched).run // Result: EnrichedUser("Alice", List("dark-mode", "notifications")) ``` -------------------------------- ### Handle optional values with valueOr and valueOrF Source: https://github.com/theiterators/sealed-monad/blob/master/docs/faq.md Shows how to convert Option-returning operations into Sealed Monad results using static or effectful fallback values. ```scala // Find a user by ID, with NotFound as the fallback userRepository.findById(userId) // IO[Option[User]] .valueOr[UserResponse](UserResponse.NotFound) // Sealed[IO, User, UserResponse] // Find a user by ID, with logging and NotFound as the fallback userRepository.findById(userId) .valueOrF( logger.warn(s"User not found: $userId") *> IO.pure(UserResponse.NotFound) ) ``` -------------------------------- ### Effectful Error Handling with attemptF in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md The attemptF operator facilitates performing effects, such as logging, during error handling. This example logs specific errors during a payment operation before returning a typed error result, offering rich failure context while maintaining a clean error channel. ```scala private def processJobPayment(job: Job): StepIO[PaymentConfirmation] = paymentService.process(job.cost).seal.attemptF { case PaymentResult.Success(confirmation) => IO.pure(Right(confirmation)) case PaymentResult.Declined => Logger[IO] .error(s"Payment declined for job ${job.id}") .as(Left(JobResult.PaymentFailed("Payment declined"))) case PaymentResult.InsufficientFunds => Logger[IO] .error(s"Insufficient funds for job ${job.id}") .as(Left(JobResult.InsufficientFunds)) case PaymentResult.ProcessingError(error) => Logger[IO] .error(s"Payment processing error for job ${job.id}: $error") .as(Left(JobResult.PaymentFailed(error))) } ``` -------------------------------- ### Testing User Registration Success in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md This ScalaTest code snippet tests the successful registration of a user. It mocks dependencies like email validation, repository checks, and user repository to ensure the `registerUser` method returns `RegisterResponse.Success` when all conditions are met. ```scala import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import cats.effect.unsafe.implicits.global class UserServiceSpec extends AnyFlatSpec with Matchers { "UserService.registerUser" should "return Success when registration succeeds" in { val service = new UserService( emailValidator = _ => IO.pure(true), emailRepository = _ => IO.pure(false), // email doesn't exist passwordValidator = _ => IO.pure(true), userRepository = (_, _) => IO.pure(User("user-123", "test@example.com")) ) val result = service.registerUser(RegisterRequest("test@example.com", "password123")) .unsafeRunSync() result shouldBe RegisterResponse.Success("user-123") } it should "return EmailInvalid when email is invalid" in { val service = new UserService( emailValidator = _ => IO.pure(false), // invalid email emailRepository = _ => IO.pure(false), passwordValidator = _ => IO.pure(true), userRepository = (_, _) => IO.pure(User("user-123", "test@example.com")) ) val result = service.registerUser(RegisterRequest("invalid", "password123")) .unsafeRunSync() result shouldBe RegisterResponse.EmailInvalid } // Additional tests for other outcomes } ``` -------------------------------- ### Provision API Keys with Sealed Monad in Scala Source: https://github.com/theiterators/sealed-monad/blob/master/docs/best-practices.md This implementation uses the sealed-monad library to orchestrate a series of validation checks for API key provisioning. It ensures that each step, such as tenant verification and quota checking, either proceeds or short-circuits to a specific error response without deep nesting. ```scala import cats.effect.IO import pl.iterators.sealedmonad.syntax._ case class Tenant(id: String, active: Boolean, plan: String) case class UsageStats(requestsToday: Int) sealed trait ProvisionKeyResponse object ProvisionKeyResponse { final case class Provisioned(apiKey: String, rateLimit: Int) extends ProvisionKeyResponse case object TenantNotFound extends ProvisionKeyResponse case object TenantSuspended extends ProvisionKeyResponse case object KeyLimitReached extends ProvisionKeyResponse case object RateLimitExceeded extends ProvisionKeyResponse } object KeyService { private val maxKeys = Map("free" -> 1, "pro" -> 5) private val rateLimits = Map("free" -> 100, "pro" -> 1000) def provisionKey(tenantId: String): IO[ProvisionKeyResponse] = { (for { tenant <- tenantRepo.find(tenantId) .valueOr(ProvisionKeyResponse.TenantNotFound) .ensure(_.active, ProvisionKeyResponse.TenantSuspended) keyCount <- tenantRepo.countKeys(tenant.id).seal maxAllowed = maxKeys.getOrElse(tenant.plan, 1) _ <- IO.pure(keyCount < maxAllowed) .ensure(identity, ProvisionKeyResponse.KeyLimitReached) stats <- usageService.getStats(tenant.id).seal rateLimit = rateLimits.getOrElse(tenant.plan, 100) _ <- IO.pure(stats.requestsToday < rateLimit) .ensure(identity, ProvisionKeyResponse.RateLimitExceeded) } yield ProvisionKeyResponse.Provisioned( apiKey = generateApiKey(), rateLimit = rateLimit )).run } } ```