Current section
Files
Jump to
Current section
Files
src/validator.gleam
import gleam/list
import gleam/result
import validator/common.{Errors, ValidatorResult}
fn curry2(constructor: fn(a, b) -> value) {
fn(a) { fn(b) { constructor(a, b) } }
}
fn curry3(constructor: fn(a, b, c) -> value) {
fn(a) { fn(b) { fn(c) { constructor(a, b, c) } } }
}
fn curry4(constructor: fn(a, b, c, d) -> value) {
fn(a) { fn(b) { fn(c) { fn(d) { constructor(a, b, c, d) } } } }
}
fn curry5(constructor: fn(a, b, c, d, e) -> value) {
fn(a) { fn(b) { fn(c) { fn(d) { fn(e) { constructor(a, b, c, d, e) } } } } }
}
fn curry6(constructor: fn(a, b, c, d, e, f) -> value) {
fn(a) {
fn(b) {
fn(c) { fn(d) { fn(e) { fn(f) { constructor(a, b, c, d, e, f) } } } }
}
}
}
/// Build a validator for a type that has one attribute
///
/// ## Example
///
/// type Person { Person(name: String) }
///
/// let validator = fn(person: Person) {
/// v.build1(person)
/// |> v.validate(person.name, name_validator)
/// }
pub fn build1(constructor) {
Ok(constructor)
}
/// Build a validator for a type that has two attributes
///
/// ## Example
///
/// type Person { Person(name: String, age: Int) }
///
/// let validator = fn(person: Person) {
/// v.build2(person)
/// |> v.validate(person.name, name_validator)
/// |> v.validate(person.age, ...)
/// }
pub fn build2(constructor) {
Ok(curry2(constructor))
}
/// Build a validator for a type that has three attributes
///
/// ## Example
///
/// type Person { Person(name: String, age: Int, email: String) }
///
/// let validator = fn(person: Person) {
/// v.build3(person)
/// |> v.validate(person.name, name_validator)
/// |> v.validate(person.age, ...)
/// |> v.validate(person.email, ...)
/// }
pub fn build3(constructor) {
Ok(curry3(constructor))
}
/// Build a validator for a type that has four attributes
pub fn build4(constructor) {
Ok(curry4(constructor))
}
/// Build a validator for a type that has five attributes
pub fn build5(constructor) {
Ok(curry5(constructor))
}
/// Build a validator for a type that has six attributes
pub fn build6(constructor) {
Ok(curry6(constructor))
}
/// Validate an attribute.
///
/// ## Example
///
/// let validator = fn(person: Person) {
/// v.build1(Person)
/// |> v.validate(person.name, v_string.is_not_empty(ErrorEmpty))
/// }
///
pub fn validate(
accumulator: Result(fn(b) -> next_accumulator, Errors(e)),
value: a,
validator: fn(a) -> Result(b, Errors(e)),
) -> Result(next_accumulator, Errors(e)) {
case validator(value) {
Ok(value) ->
accumulator
|> result.map(fn(acc) { acc(value) })
Error(tuple(e, errors)) ->
case accumulator {
Ok(_) ->
Error(tuple(e, errors))
Error(tuple(first_error, previous_errors)) ->
Error(
tuple(
first_error,
list.flatten([previous_errors, errors]),
)
)
}
}
}
/// Keep a value as is.
///
/// ## Example
///
/// fn person_validor(person: Person) {
/// v.build2(Person)
/// |> v.validate(person.name, ...)
/// |> v.keep(person.age)
/// }
///
pub fn keep(
accumulator: Result(fn(value) -> next_accumulator, Errors(e)),
value: value,
) -> Result(next_accumulator, Errors(e)) {
case accumulator {
Error(errors) ->
Error(errors)
Ok(acc) ->
Ok(acc(value))
}
}
/// Create a custom validator
///
/// A custom validator has two attributes:
///
/// - The error
/// - A check function
///
/// The check function is a function that takes an `input` and returns `Option(output)`
///
/// ## Example
///
/// let must_be_sam = fn(name: String) -> Option(String) {
/// case name == "Sam" {
/// True -> Some(name)
/// False -> None
/// }
/// }
///
/// let validator = fn(person: Person) {
/// v.build1(Person)
/// |> v.validate(person.name, v.custom("Not Sam", must_be_sam))
/// }
pub fn custom(error, check) {
common.custom(error, check)
}
/// Compose validators
///
/// Run the first validator and if successful then the second.
/// Only returns the first error.
///
/// ## Example
///
/// let name_validator = v_string.is_not_empty("Empty")
/// |> v.and(v_string.min_length("Must be at least six", 6))
pub fn and(
validator1: common.Validator(i, mid, e),
validator2: common.Validator(mid, o, e)
) -> common.Validator(i, o, e) {
fn(input: i) {
validator1(input)
|> result.then(validator2)
}
}
/// Validate a value using a list of validators.
/// This runs all the validators in the list.
///
/// The initial input is passed to all validators.
/// All these validators must have the same input and output types.
///
/// Returns Ok when all validators pass.
/// Returns Error when any validator fails. Error will have all failures.
///
/// ## Example
///
/// let name_validator = v.all([
/// v_string.is_not_empty("Empty"),
/// v_string.min_length(">=3", 3),
/// v_string.max_length("<=10", 10)
/// ])
///
/// let validator = fn(person: Person) {
/// v.build1(person)
/// |> v.validate(person.name, name_validator)
/// }
pub fn all(
validators: List(common.Validator(io, io, e))
) -> common.Validator(io, io, e) {
fn(input: io) -> Result(io, Errors(e)) {
let results = validators
|> list.map(fn(validator) {
validator(input)
})
let errors = results
|> list.map(fn(result) {
case result {
Ok(_) -> []
Error(tuple(first, rest)) -> rest
}
})
|> list.flatten
case list.head(errors) {
Error(Nil) ->
Ok(input)
Ok(head) ->
Error(tuple(head, errors))
}
}
}
/// Validate a structure as a whole.
///
/// Sometimes we need to validate a property in relation to another.
///
/// This function requires a check function like:
///
/// fn(a) -> Result(a, error)
///
/// ## Example
///
/// let strengh_and_level_validator = fn(c: Character) {
/// case c.level > c.strength {
/// True -> Error(error)
/// False -> Ok(c)
/// }
/// }
///
/// let validator = fn(c: Character) {
/// v.build2(Character)
/// |> v.validate(c.level, v_int.min("Level must be more that zero", 1))
/// |> v.validate(c.strength, v_int.min("Strength must be more that zero", 1))
/// |> v.whole(strengh_and_level_validator)
/// }
///
pub fn whole(validator: fn(whole) -> Result(whole, error)) {
fn(validation_result: ValidatorResult(whole, error)) {
validation_result
|> result.then(fn(validated: whole) {
validator(validated)
|> result.map_error(fn(error) {
tuple(error, [error])
})
})
}
}