howdy/validate
Validation of decoded input before it reaches a service.
Rules never change a value’s type, so every field is checked and all errors are reported together rather than stopping at the first.
pub fn validate(input: NewUserInput) -> validate.Result(NewUser) {
use name <- validate.field("name", input.name, [
validate.trim(),
validate.not_empty(),
validate.max_length(50),
])
use age <- validate.field("age", input.age, [validate.min(13)])
validate.ok(NewUser(name:, age:))
}
Types
One problem with one field.
pub type FieldError =
service.FieldError
The outcome of validating a whole input.
pub type Result(a) =
Result(a, List(service.FieldError))
A rule checks a value and may also normalise it, such as trimming. On
failure it returns the problem, which howdy/problem puts into words.
pub type Rule(a) =
fn(a) -> Result(a, problem.Problem)
Values
pub fn check(
ctx: context.Context(guarded),
result: Result(a, List(service.FieldError)),
next: fn(a) -> response.Response(content.Content),
) -> response.Response(content.Content)
Run a validation result in a handler. On failure a 422 is returned with
every field error and the continuation never runs.
pub fn custom(
check: fn(a) -> Bool,
problem: problem.Problem,
) -> fn(a) -> Result(a, problem.Problem)
Build a rule from a predicate and the problem reported when it fails,
such as problem.Custom("taken", "is already taken").
pub fn email() -> fn(String) -> Result(String, problem.Problem)
A light check: something before and after a single @, and a dot in the
part after it. Deliberately permissive; only a delivery attempt can prove
an address.
pub fn field(
name: String,
value: a,
rules: List(fn(a) -> Result(a, problem.Problem)),
next: fn(a) -> Result(b, List(service.FieldError)),
) -> Result(b, List(service.FieldError))
Validate one field with a list of rules, run in order. The first failing rule produces the field’s error. The continuation always runs so that later fields are checked too; its result is discarded if any field failed.
pub fn max(
maximum: Int,
) -> fn(Int) -> Result(Int, problem.Problem)
pub fn max_length(
length: Int,
) -> fn(String) -> Result(String, problem.Problem)
pub fn min(
minimum: Int,
) -> fn(Int) -> Result(Int, problem.Problem)
pub fn min_length(
length: Int,
) -> fn(String) -> Result(String, problem.Problem)
pub fn not_empty() -> fn(String) -> Result(
String,
problem.Problem,
)
pub fn ok(value: a) -> Result(a, List(service.FieldError))
Finish a validator with the validated value.
pub fn one_of(
allowed: List(a),
show: fn(a) -> String,
) -> fn(a) -> Result(a, problem.Problem)
The value must be one of allowed. show writes each option for the
problem’s allowed.
pub fn run(
rules: List(fn(a) -> Result(a, problem.Problem)),
value: a,
) -> Result(a, problem.Problem)
Run rules against a value directly, without a field name.
pub fn trim() -> fn(String) -> Result(String, problem.Problem)
Remove leading and trailing whitespace. Never fails. Put it before other string rules so they see the trimmed value.