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