All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.arrow.exact.Exact.kt Maven / Gradle / Ivy

package arrow.exact

import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.ensure

/**
 * 
 *
 * Exact allows automatically projecting smart-constructors on a `Companion Object`. We can for
 * example easily create a `NotBlankString` type that is a `String` that is not blank, leveraging
 * the Arrow's [Raise] DSL to [ensure] the value is not blank.
 *
 * ```kotlin
 * import arrow.core.raise.Raise
 * import arrow.exact.Exact
 * import arrow.exact.ExactError
 * import arrow.exact.ensure
 * import kotlin.jvm.JvmInline
 *
 * @JvmInline
 * value class NotBlankString private constructor(val value: String) {
 *   companion object : Exact {
 *     override fun Raise.spec(raw: String): NotBlankString {
 *       ensure(raw.isNotBlank())
 *       return NotBlankString(raw)
 *     }
 *   }
 * }
 * ```
 *
 * We can then easily create values of `NotBlankString` [from] a `String`, which returns us a
 * [Either] with the [ExactError] or the `NotBlankString`. We can also use [fromOrNull] to get a
 * nullable value, or [fromOrThrow] to throw an [ExactException].
 *
 * **note:** Make sure to define your constructor as `private` to prevent creating invalid values.
 *
 * ```kotlin
 * fun example() {
 *   println(NotBlankString.from("Hello"))
 *   println(NotBlankString.from(""))
 * }
 * ```
 *
 * The output of the above program is:
 * ```text
 * Either.Right(NotBlankString(value=Hello))
 * Either.Left(ExactError(message=Failed condition.))
 * ```
 * 
 * 
 *
 * You can also define [Exact] by using Kotlin delegation.
 * 
 * ```kotlin
 * @JvmInline
 * value class NotBlankString private constructor(val value: String) {
 *   companion object : Exact by Exact({
 *     ensure(it.isNotBlank())
 *     NotBlankString(it)
 *   })
 * }
 * ```
 * 
 *
 * You can define a second type `NotBlankTrimmedString` that is a `NotBlankString` that is also
 * trimmed. [ensure] allows us to compose [Exact] instances and easily
 * reuse the `NotBlankString` type.
 * 
 * ```kotlin
 * @JvmInline
 * value class NotBlankTrimmedString private constructor(val value: String) {
 *   companion object : Exact {
 *     override fun Raise.spec(raw: String): NotBlankTrimmedString {
 *       ensure(raw, NotBlankString)
 *       return NotBlankTrimmedString(raw.trim())
 *     }
 *   }
 * }
 * ```
 * 
 *
 * @see ExactEither if you need to return an [Either] with a custom error type.
 */
public fun interface Exact : ExactEither

// TODO: Should we just use `String` ???
public data class ExactError(val message: String)

/**
 * A more generic version of [Exact] that allows working over a custom error type rather than
 * [ExactError]. Since [Exact] is a specialization of [ExactEither], where [E] is fixed to
 * [ExactError], we can easily combine the two by mapping from [ExactError] to our custom [E] type.
 *
 * 
 * ```kotlin
 * sealed interface UsernameError {
 *   object Invalid : UsernameError
 *   data class Offensive(val username: String) : UsernameError
 * }
 *
 * @JvmInline
 * value class Username private constructor(val value: String) {
 *   companion object : ExactEither {
 *     override fun Raise.spec(raw: String): Username {
 *       val username =
 *         ensure(raw, NotBlankTrimmedString) {
 *           UsernameError.Invalid
 *         }.value
 *       ensure(username.length < 100) { UsernameError.Invalid }
 *       ensure(username !in listOf("offensive")) { UsernameError.Offensive(username) }
 *       return Username(username)
 *     }
 *   }
 * }
 * ```
 * 
 */
public fun interface ExactEither {

  public fun Raise.spec(raw: A): R

  public fun from(value: A): Either = either { spec(value) }

  public fun fromOrNull(value: A): R? = from(value).getOrNull()

  public fun fromOrThrow(value: A): R =
    when (val result = from(value)) {
      is Either.Left -> throw ExactException(result.value)
      is Either.Right -> result.value
    }
}

/** The exception that is thrown by [ExactEither.fromOrThrow] when the value is invalid. */
public class ExactException(error: Any) : IllegalArgumentException("ArrowExact error: $error")




© 2015 - 2025 Weber Informatics LLC | Privacy Policy