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

commonMain.dev.nesk.akkurate.arrow.Arrow.kt Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dev.nesk.akkurate.arrow

import arrow.core.*
import arrow.core.raise.Raise
import arrow.core.raise.either
import dev.nesk.akkurate.ValidationResult
import dev.nesk.akkurate.constraints.ConstraintViolation
import dev.nesk.akkurate.constraints.ConstraintViolationSet

private fun ConstraintViolationSet.toNonEmptySet(): NonEmptySet =
    nonEmptySetOf(this.first(), *this.drop(1).toTypedArray())

/**
 * Transforms the [ValidationResult] to [Either.Right] of [T], or [Either.Left] of [ConstraintViolation] list.
 *
 * You can convert the result of the validation to an [Either] and apply transformations to it:
 * ```
 * val validateBookName = Validator { isNotBlank() }
 *
 * fun normalizeBookName(bookName: String) = validateBookName(bookName)
 *     .toEither()
 *     .fold(
 *         ifLeft = { "" },
 *         ifRight = { it.trim() },
 *     )
 * ```
 *
 * A non-blank string is trimmed because the validation was successful and went through the `ifRight` block:
 * ```
 * normalizeBookName("  The Lord of the Rings  ") // Returns: "The Lord of the Rings"
 * ```
 *
 * Whereas, a blank string fails the validation and goes through the `ifLeft` block:
 * ```
 * normalizeBookName("  ") // Returns: ""
 * ```
 */
public fun  ValidationResult.toEither(): Either, T> = when (this) {
    is ValidationResult.Failure -> violations.toNonEmptySet().left()
    is ValidationResult.Success -> value.right()
}

/**
 * Binds the [ValidationResult] to the [Raise] computation.
 *
 * You can bind the result of the validation to a `Raise` computation:
 * ```
 * val validateBookName = Validator { isNotBlank() }
 * val validateAuthorName = Validator { isNotBlank() }
 *
 * fun combineBookAndAuthor(bookName: String, authorName: String) = either {
 *     val book = bind(validateBookName(bookName))
 *     val author = bind(validateAuthorName(authorName))
 *     "${book.trim()} by ${author.trim()}"
 * }
 * ```
 *
 * Non-blank strings returns [Either.Right] because the validation was successful and the [either] block could complete:
 * ```
 * combineBookAndAuthor("  The Lord of the Rings  ", "  J. R. R. Tolkien  ")
 * // Returns: Either.Right(value = "The Lord of the Rings by J. R. R. Tolkien")
 * ```
 *
 * Whereas, a single blank string fails the validation and interrupts the [either] block, returning a [Either.Left] of constraint violations:
 * ```
 * normalizeBookName("  ", "  J. R. R. Tolkien  ") // Returns: Either.Left>
 * ```
 *
 * @return The validated value on successful result.
 */
public fun  Raise>.bind(validationResult: ValidationResult): T = when (validationResult) {
    is ValidationResult.Failure -> raise(validationResult.violations.toNonEmptySet())
    is ValidationResult.Success -> validationResult.value
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy