diff --git a/README.md b/README.md index e8a33fc..29744b9 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,17 @@ Exact allows automatically projecting smart-constructors on a `Companion Object` 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 + +```kotlin @JvmInline value class NotBlankString private constructor(val value: String) { companion object : Exact { - override fun Raise.spec(raw: String): NotBlankString { + override fun Raise.spec(raw: String): NotBlankString { ensure(raw.isNotBlank()) return NotBlankString(raw) } @@ -45,12 +45,46 @@ The output of the above program is: ```text Either.Right(NotBlankString(value=Hello)) -Either.Left(ExactError(message=Failed condition.)) +Either.Left(Failed condition.) ``` +By default, if no error message is provided it'll use `Failed condition.`, +but just like `require` from the Kotlin Standard Library you can provide your own error message. + + +```kotlin +@JvmInline +value class NotBlankString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { "String must not be blank." } + return NotBlankString(raw) + } + } +} + +fun example() { + println(NotBlankString.from("")) +} +``` + +The output of the above program is: + +```text +Either.Left(String must not be blank.) +``` + + + + You can also define `Exact` by using Kotlin delegation. + You can define a second type `NotBlankTrimmedString` that is a `NotBlankString` that is also trimmed. `ensureExact` allows us to compose `Exact` instances and easily @@ -74,14 +108,13 @@ reuse the `NotBlankString` type. + diff --git a/guide/src/commonMain/kotlin/examples/example-exact-01.kt b/guide/src/commonMain/kotlin/examples/example-exact-01.kt index b0acbaf..6728796 100644 --- a/guide/src/commonMain/kotlin/examples/example-exact-01.kt +++ b/guide/src/commonMain/kotlin/examples/example-exact-01.kt @@ -3,14 +3,13 @@ package arrow.exact.knit.example.exampleExact01 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 { + override fun Raise.spec(raw: String): NotBlankString { ensure(raw.isNotBlank()) return NotBlankString(raw) } diff --git a/guide/src/commonMain/kotlin/examples/example-exact-02.kt b/guide/src/commonMain/kotlin/examples/example-exact-02.kt index f7f1d59..84b31f5 100644 --- a/guide/src/commonMain/kotlin/examples/example-exact-02.kt +++ b/guide/src/commonMain/kotlin/examples/example-exact-02.kt @@ -1,14 +1,21 @@ // This file was automatically generated from Exact.kt by Knit tool. Do not edit. package arrow.exact.knit.example.exampleExact02 +import arrow.core.raise.Raise import arrow.exact.Exact -import arrow.exact.ensure +import arrow.core.raise.ensure import kotlin.jvm.JvmInline @JvmInline value class NotBlankString private constructor(val value: String) { - companion object : Exact by Exact({ - ensure(it.isNotBlank()) - NotBlankString(it) - }) + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { "String must not be blank." } + return NotBlankString(raw) + } + } +} + +fun example() { + println(NotBlankString.from("")) } diff --git a/guide/src/commonMain/kotlin/examples/example-exact-03.kt b/guide/src/commonMain/kotlin/examples/example-exact-03.kt index c8378b9..1036cc0 100644 --- a/guide/src/commonMain/kotlin/examples/example-exact-03.kt +++ b/guide/src/commonMain/kotlin/examples/example-exact-03.kt @@ -1,27 +1,14 @@ // This file was automatically generated from Exact.kt by Knit tool. Do not edit. package arrow.exact.knit.example.exampleExact03 -import arrow.core.raise.Raise import arrow.exact.Exact -import arrow.exact.ExactError import arrow.exact.ensure import kotlin.jvm.JvmInline -class NotBlankString private constructor(val value: String) { - companion object : Exact { - override fun Raise.spec(raw: String): NotBlankString { - ensure(raw.isNotBlank()) - return NotBlankString(raw) - } - } -} - @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()) - } - } +value class NotBlankString private constructor(val value: String) { + companion object : Exact by Exact({ + ensure(it.isNotBlank()) + NotBlankString(it) + }) } diff --git a/guide/src/commonMain/kotlin/examples/example-exact-04.kt b/guide/src/commonMain/kotlin/examples/example-exact-04.kt index 75b2568..cbae6c7 100644 --- a/guide/src/commonMain/kotlin/examples/example-exact-04.kt +++ b/guide/src/commonMain/kotlin/examples/example-exact-04.kt @@ -2,39 +2,25 @@ package arrow.exact.knit.example.exampleExact04 import arrow.core.raise.Raise -import arrow.core.raise.ensure import arrow.exact.Exact -import arrow.exact.ExactEither -import arrow.exact.ExactError import arrow.exact.ensure import kotlin.jvm.JvmInline -@JvmInline -value class NotBlankTrimmedString private constructor(val value: String) { - companion object : Exact { - override fun Raise.spec(raw: String): NotBlankTrimmedString { +class NotBlankString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { ensure(raw.isNotBlank()) - return NotBlankTrimmedString(raw.trim()) + return NotBlankString(raw) } } } -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) +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()) } } } diff --git a/guide/src/commonMain/kotlin/examples/example-exact-05.kt b/guide/src/commonMain/kotlin/examples/example-exact-05.kt new file mode 100644 index 0000000..111ae36 --- /dev/null +++ b/guide/src/commonMain/kotlin/examples/example-exact-05.kt @@ -0,0 +1,39 @@ +// This file was automatically generated from Exact.kt by Knit tool. Do not edit. +package arrow.exact.knit.example.exampleExact05 + +import arrow.core.raise.Raise +import arrow.core.raise.ensure +import arrow.exact.Exact +import arrow.exact.ExactEither +import arrow.exact.ensure +import kotlin.jvm.JvmInline + +@JvmInline +value class NotBlankTrimmedString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankTrimmedString { + ensure(raw.isNotBlank()) + return NotBlankTrimmedString(raw.trim()) + } + } +} + +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) + } + } +} diff --git a/guide/src/commonMain/kotlin/examples/example-readme-01.kt b/guide/src/commonMain/kotlin/examples/example-readme-01.kt index cab6297..8acfc01 100644 --- a/guide/src/commonMain/kotlin/examples/example-readme-01.kt +++ b/guide/src/commonMain/kotlin/examples/example-readme-01.kt @@ -3,14 +3,13 @@ package arrow.exact.knit.example.exampleReadme01 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 { + override fun Raise.spec(raw: String): NotBlankString { ensure(raw.isNotBlank()) return NotBlankString(raw) } diff --git a/guide/src/commonMain/kotlin/examples/example-readme-02.kt b/guide/src/commonMain/kotlin/examples/example-readme-02.kt index 31a06a8..b1159fa 100644 --- a/guide/src/commonMain/kotlin/examples/example-readme-02.kt +++ b/guide/src/commonMain/kotlin/examples/example-readme-02.kt @@ -1,14 +1,21 @@ // This file was automatically generated from README.md by Knit tool. Do not edit. package arrow.exact.knit.example.exampleReadme02 +import arrow.core.raise.Raise import arrow.exact.Exact -import arrow.exact.ensure +import arrow.core.raise.ensure import kotlin.jvm.JvmInline @JvmInline -value class NotBlankString private constructor(val value: String) { - companion object : Exact by Exact({ - ensure(it.isNotBlank()) - NotBlankString(it) - }) +value class NotBlankString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { "String must not be blank." } + return NotBlankString(raw) + } + } +} + +fun example() { + println(NotBlankString.from("")) } diff --git a/guide/src/commonMain/kotlin/examples/example-readme-03.kt b/guide/src/commonMain/kotlin/examples/example-readme-03.kt index 653192b..ae4e61c 100644 --- a/guide/src/commonMain/kotlin/examples/example-readme-03.kt +++ b/guide/src/commonMain/kotlin/examples/example-readme-03.kt @@ -1,28 +1,14 @@ // This file was automatically generated from README.md by Knit tool. Do not edit. package arrow.exact.knit.example.exampleReadme03 -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) - } - } -} - -@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()) - } - } + companion object : Exact by Exact({ + ensure(it.isNotBlank()) + NotBlankString(it) + }) } diff --git a/guide/src/commonMain/kotlin/examples/example-readme-04.kt b/guide/src/commonMain/kotlin/examples/example-readme-04.kt new file mode 100644 index 0000000..c560116 --- /dev/null +++ b/guide/src/commonMain/kotlin/examples/example-readme-04.kt @@ -0,0 +1,27 @@ +// This file was automatically generated from README.md by Knit tool. Do not edit. +package arrow.exact.knit.example.exampleReadme04 + +import arrow.core.raise.Raise +import arrow.exact.Exact +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) + } + } +} + +@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()) + } + } +} diff --git a/guide/src/jvmTest/kotlin/examples/spec/ExactExampleSpec.kt b/guide/src/jvmTest/kotlin/examples/spec/ExactExampleSpec.kt index fe8656d..2789f16 100644 --- a/guide/src/jvmTest/kotlin/examples/spec/ExactExampleSpec.kt +++ b/guide/src/jvmTest/kotlin/examples/spec/ExactExampleSpec.kt @@ -10,7 +10,14 @@ class ExactExampleSpec : StringSpec({ captureOutput("ExampleExact01") { arrow.exact.knit.example.exampleExact01.example() } .verifyOutputLines( "Either.Right(NotBlankString(value=Hello))", - "Either.Left(ExactError(message=Failed condition.))" + "Either.Left(Failed condition.)" + ) + } + + "ExampleExact02" { + captureOutput("ExampleExact02") { arrow.exact.knit.example.exampleExact02.example() } + .verifyOutputLines( + "Either.Left(String must not be blank.)" ) } diff --git a/guide/src/jvmTest/kotlin/examples/spec/ReadMeSpec.kt b/guide/src/jvmTest/kotlin/examples/spec/ReadMeSpec.kt index a2a2d48..d0a458d 100644 --- a/guide/src/jvmTest/kotlin/examples/spec/ReadMeSpec.kt +++ b/guide/src/jvmTest/kotlin/examples/spec/ReadMeSpec.kt @@ -10,7 +10,14 @@ class ReadMeSpec : StringSpec({ captureOutput("ExampleReadme01") { arrow.exact.knit.example.exampleReadme01.example() } .verifyOutputLines( "Either.Right(NotBlankString(value=Hello))", - "Either.Left(ExactError(message=Failed condition.))" + "Either.Left(Failed condition.)" + ) + } + + "ExampleReadme02" { + captureOutput("ExampleReadme02") { arrow.exact.knit.example.exampleReadme02.example() } + .verifyOutputLines( + "Either.Left(String must not be blank.)" ) } diff --git a/src/commonMain/kotlin/arrow/exact/Exact.kt b/src/commonMain/kotlin/arrow/exact/Exact.kt index a2d3234..f104c73 100644 --- a/src/commonMain/kotlin/arrow/exact/Exact.kt +++ b/src/commonMain/kotlin/arrow/exact/Exact.kt @@ -15,14 +15,13 @@ import arrow.core.raise.ensure * ```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 { + * override fun Raise.spec(raw: String): NotBlankString { * ensure(raw.isNotBlank()) * return NotBlankString(raw) * } @@ -31,7 +30,7 @@ import arrow.core.raise.ensure * ``` * * We can then easily create values of `NotBlankString` [ExactEither.from] a `String`, which returns us a - * [Either] with the [ExactError] or the `NotBlankString`. We can also use [ExactEither.fromOrNull] to get a + * [Either] with the [String] or the `NotBlankString`. We can also use [ExactEither.fromOrNull] to get a * nullable value, or [ExactEither.fromOrThrow] to throw an [ExactException]. * * **note:** Make sure to define your constructor as `private` to prevent creating invalid values. @@ -46,11 +45,44 @@ import arrow.core.raise.ensure * The output of the above program is: * ```text * Either.Right(NotBlankString(value=Hello)) - * Either.Left(ExactError(message=Failed condition.)) + * Either.Left(Failed condition.) * ``` * * * + * By default, if no error message is provided it'll use `Failed condition.`, + * but just like `require` from the Kotlin Standard Library you can provide your own error message. + * + * ```kotlin + * import arrow.core.raise.Raise + * import arrow.exact.Exact + * import arrow.core.raise.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()) { "String must not be blank." } + * return NotBlankString(raw) + * } + * } + * } + * + * fun example() { + * println(NotBlankString.from("")) + * } + * ``` + * + * The output of the above program is: + * + * ```text + * Either.Left(String must not be blank.) + * ``` + * + * + * + * * You can also define [Exact] by using Kotlin delegation. * + * * * You can define a second type `NotBlankTrimmedString` that is a `NotBlankString` that is also * trimmed. [ensure] allows us to compose [Exact] instances and easily @@ -74,13 +106,12 @@ import arrow.core.raise.ensure * + * * * @see ExactEither if you need to return an [Either] with a custom error type. */ -public typealias Exact = ExactEither - -// TODO: Should we just use `String` ??? -public data class ExactError(val message: String) +public typealias Exact = ExactEither /** * Input more generic version of [Exact] that allows working over a custom error type rather than - * [ExactError]. Since [Exact] is a specialization of [ExactEither], where [Error] is fixed to - * [ExactError], we can easily combine the two by mapping from [ExactError] to our custom [Error] type. + * [String]. Since [Exact] is a specialization of [ExactEither], where [Error] is fixed to + * [String], we can easily combine the two by mapping from [String] to our custom [Error] type. * * + * */ public fun interface ExactEither { diff --git a/src/commonMain/kotlin/arrow/exact/ExactDsl.kt b/src/commonMain/kotlin/arrow/exact/ExactDsl.kt index 4a38596..d48ecef 100644 --- a/src/commonMain/kotlin/arrow/exact/ExactDsl.kt +++ b/src/commonMain/kotlin/arrow/exact/ExactDsl.kt @@ -5,13 +5,13 @@ import arrow.core.raise.Raise import arrow.core.raise.RaiseDSL @RaiseDSL -public fun Raise.ensure(condition: Boolean) { - if (!condition) raise(ExactError("Failed condition.")) +public fun Raise.ensure(condition: Boolean) { + if (!condition) raise("Failed condition.") } @RaiseDSL -public fun Raise.ensure(raw: A, exact: ExactEither<*, A, B>): B = - ensure(raw, exact) { ExactError("Failed to match Exact.") } +public inline fun Raise.ensure(raw: A, exact: ExactEither<*, A, B>): B = + ensure(raw, exact) { "Failed to match Exact." } @RaiseDSL public inline fun Raise.ensure(raw: A, exact: ExactEither, error: (Error) -> E): B {