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.

✨ Search Document