arrow-exact
arrow-exact copied to clipboard
Impure validation
Although arrow-exact is originally meant for type refinements, our current DSL can handle any kind of validation.
In my projects, I have two major kinds of validation needs: pure and impure. So far, we have concentrated on pure validation (and I think we're doing a good job of it).
Here's an example of impure validation, that I have seen in the real world (for the curious). We can simplify this example to: the class we want to do validation on is called Reference
. It stores an identifier to another business entity called File
(here, we don't care what it is). A Reference
is only valid if the referenced File
exists at the time of instantiation. For this, we need two things:
-
suspend
, to make a network request - an instance of
FileService
Here's an example of how it could look like:
fun interface ImpureExact<out E, A, in C, out R> {
suspend fun from(value: A, context: C): Either<E, R>
// …
}
// If we just need suspension but no context, we provide a simplified interface
fun interface SuspendExact<out E, A, out R> : ImpureExact<E, A, Unit, R> {
suspend fun from(value: A): Either<E, R> = from(value, Unit)
// …
}
Usage:
data class Ref private constructor(
val id: String,
) {
companion object : ImpureExact<String, String, FileService, Ref> by exact({ it, service ->
ensure(ExactId)
val file = service.find(it)
ensureNotNull(file) { "Could not find file $it" }
Ref(it)
})
}
suspend fun main() {
val service = FileService(…)
val id = service.create(…)
Ref.fromOrThrow(id)
}
What do you think?