Akkurate 0.11.0 Help

Arrow

Arrow is a functional programming library, for which Akkurate provides an integration to convert its validation results to Arrow's typed errors.

This article will show you what conversions are possible and how they can help you bridge the gap between Akkurate and Arrow.

A code example of the Arrow integration used to showcase Akkurate on social networks.

Installation

Before using Akkurate with Arrow, you need to add the following dependency to your Gradle script:

Install Akkurate's support library for Arrow

implementation("dev.nesk.akkurate:akkurate-arrow:0.11.0")

    Convert to the Either type

    By calling toEither, you can convert the validation result to an Either.

    Take the following validator:

    @Validate data class Book(val title: String) val validateBook = Validator<Book> { title.isNotEmpty() }

    You obtain a validation result by calling the validateBook() function:

    val validBook = Book(title = "The Lord of the Rings") val result: ValidationResult<Book> = validateBook(validBook)

    Then, you can convert it by calling the toEither() extension function:

    val eitherResult: Either<NonEmptySet<ConstraintViolation>, Book> = result.toEither()

    On a successful validation, you can read the validated value; on a failed one, you can list all the constraint violations:

    when (eitherResult) { is Either.Left -> { println("The book is invalid: ${eitherResult.value}") } is Either.Right -> { println("The book is valid: ${eitherResult.value}") } }

    However, converting the validation result to an Either type doesn't bring much if you only use when expressions. Arrow is a functional library after all, so you should probably take advantage of it:

    val message = eitherResult .map { "The book is valid: $it" } .getOrElse { "The book is invalid: $it" } println(message)

    Bind to Raise computation

    When working with Arrow's functional programming paradigms, the bind function is crucial in handling validations seamlessly, especially when dealing with multiple Either types. This is particularly useful when you're already inside a Raise computation.

    Let's consider an example where we have multiple data classes to validate:

    @Validate data class Book(val title: String) @Validate data class Author(val name: String) val validateBook = Validator<Book> { title.isNotEmpty() } val validateAuthor = Validator<Author> { name.isNotEmpty() }

    In a typical scenario, you might want to validate a Book and an Author within the same context. Without bind, you would convert each validation result to an Either and handle them separately, which can be cumbersome:

    // Validate the data and convert the results to Either val bookResult = validateBook(Book("The Lord of the Rings")).toEither() val authorResult = validateAuthor(Author("J.R.R. Tolkien")).toEither() // Handle the validation results separately val book = when (bookResult) { is Either.Left -> throw IllegalArgumentException("Invalid book") is Either.Right -> bookResult.value } val author = when (authorResult) { is Either.Left -> throw IllegalArgumentException("Invalid author") is Either.Right -> authorResult.value } // Further computation with the validated data println("Validated book: $book") println("Validated author: $author")

    However, using bind allows you to compute over the happy path by automatically handling errors and focusing on successful outcomes. When a validation fails, bind short-circuits the computation, eliminating the need for explicit error checking and handling within the either block.

    either { // Directly bind the validation results val book = bind(validateBook(Book("The Lord of the Rings"))) val author = bind(validateAuthor(Author("J.R.R. Tolkien"))) // Further computation with the validated data println("Validated book: $book") println("Validated author: $author") }
    Last modified: 19 December 2024