validate_monadic
Types
Simple type alias for a non-empty list. A non-empty list is just a structure containing the first item of the list, followed by the rest of the list. You can find a more useful implementation here . For the purposes of not including an extra dependency, we just use a tuple here.
pub type ErrorList(error) =
#(error, List(error))
Simple type alias over a Result with a non-empty list off generic errors
for the Error branch. The non-empty list is important here. For validation we must
represent a Result type that can have multiple errors, but we must avoid allowing
something like Error([])
in which we can have an error branch but no errors!
pub type Validation(validated, error) =
Result(validated, ErrorList(error))
Functions
pub fn and_also(
validation_a: Result(a, #(b, List(b))),
validation_b: Result(a, #(b, List(b))),
) -> Result(a, #(b, List(b)))
Combine two validation results into one. This mainly for merging errors. The Ok
branch of the
last validation supplied will be the returned Ok
branch. This is used internally by compose
pub fn and_map(
prev prev_validation: Result(fn(a) -> b, #(c, List(c))),
next validation: Result(a, #(c, List(c))),
) -> Result(b, #(c, List(c)))
Used to create applicative chains of validation. This is very important for combining validation of fields into the validation of an entire form.
let validate_form = function.curry3(fn (
ValidFirstName,
ValidLastName,
ValidAge
) {
ValidForm(ValidFirstName, ValidLastName, ValidAge)
})
let validation_result =
validate.succeed(validate_form)
|> validate.and_map(first_name_result)
|> validate.and_map(last_name_result)
|> validate.and_map(age_result)
pub fn and_then(
over validation: Result(a, #(b, List(b))),
bind bind_fn: fn(a) -> Result(c, #(b, List(b))),
) -> Result(c, #(b, List(b)))
Specify a validation that will run after a given validation, using its result. This is very useful for validations that need to run after a transform is attempted.
let age_result =
form.age_string
|> is_parsable_int
|> validate.and_then(int_less_than(_, 101))
It can easily be used in conjuction with compose
let age_result =
form.age_string
|> is_parsable_int
|> validate.and_then(
validate.compose(int_less_than(_, 101), [int_greater_than(_, 0)])
)
pub fn compose(
input: a,
validation: fn(a) -> Result(b, #(c, List(c))),
validations: List(fn(a) -> Result(b, #(c, List(c)))),
) -> Result(b, #(c, List(c)))
Compose together multiple validations. This combine the errors of all validations that fail, and does not stop at the first failure. Takes the input to be validated as the first argument, then a non-empty list of unary functions that take that input and return a validation result of the same type.
let validation_result =
raw_field
|> validate.compose(no_numbers, [shorter_than_10, no_symbols, no_whitespace])
pub fn error(err: a) -> Result(b, #(a, List(a)))
Convenience function for lifting a single error into our non-empty list error.
pub fn map(
over validation: Result(a, #(b, List(b))),
with map_fn: fn(a) -> c,
) -> Result(c, #(b, List(b)))
Map a validation type to another type. This is often useful to nest the result of a validation
into a “ValidatedType”. This is just an alias over result.map
pub type ValidatedLastName {
ValidatedLastName(String)
}
// ...
let last_name_result =
form.last_name
|> string_non_empty
|> validate.map(ValidatedLastName)
pub fn map_error(
over validation: Result(a, #(b, List(b))),
with map_fn: fn(b) -> c,
) -> Result(a, #(c, List(c)))
Map over all the errors for a validation result. This is very useful for cases where you have re-usable validators with generic error messages, and you wish to specify the errors are associated with a specific field
let validation_result =
raw_field
|> validate.compose(no_numbers, [shorter_than_10])
|> validate.map_error(string.append("Field Name Error: ", _))