Drafter.Validation (drafter v0.3.2)

Copy Markdown View Source

Input validation for TUI widgets.

A validator is one of:

  • a one-argument function returning :ok or {:error, message}
  • :required or :email
  • {:min_length, min}, {:max_length, max}, {:pattern, regex_or_string}, {:range, min, max}, {:custom, fun}
  • any of the above tuple forms with a trailing message string that replaces the default one, e.g. {:max_length, 100, "Too long"}; :required and :email take {:required, message} and {:email, message}

Every check but :required treats a value of the wrong type as a failure, so a non-binary fails :min_length and a non-number fails :range.

iex> Drafter.Validation.validate("a@b.co", [:required, :email])
:ok

iex> Drafter.Validation.validate("", [{:required, "Name needed"}])
{:error, "Name needed"}

Summary

Types

What every check returns: :ok, or {:error, message} with a display string.

Every form run_validator/2 accepts.

Functions

Wrap a list of validators as {:combined, validators}.

Wrap a one-argument function as the validator {:custom, fun}.

Fails unless value is a binary of the form local@domain.tld, with no spaces or further @.

Fails unless value is a binary of at most max graphemes.

Fails unless value is a binary of at least min graphemes.

Fails unless regex matches somewhere in value.

Fails unless value is a number in min..max inclusive.

Fails when value is nil, "" or []; anything else passes.

Run one validator against value.

Run validators against value in order.

Types

validation_result()

@type validation_result() :: :ok | {:error, String.t()}

What every check returns: :ok, or {:error, message} with a display string.

validator()

@type validator() ::
  (any() -> validation_result())
  | :required
  | :email
  | {:required, String.t()}
  | {:email, String.t()}
  | {:min_length, non_neg_integer()}
  | {:min_length, non_neg_integer(), String.t()}
  | {:max_length, non_neg_integer()}
  | {:max_length, non_neg_integer(), String.t()}
  | {:pattern, Regex.t() | String.t()}
  | {:pattern, Regex.t() | String.t(), String.t()}
  | {:range, number(), number()}
  | {:range, number(), number(), String.t()}
  | {:custom, (any() -> validation_result())}

Every form run_validator/2 accepts.

Any other term raises FunctionClauseError — including {:combined, validators} as returned by combine/1.

Functions

combine(validators)

@spec combine([validator()]) :: {:combined, [validator()]}

Wrap a list of validators as {:combined, validators}.

run_validator/2 has no clause for this shape, so pass the list to validate/2 instead of combining it.

custom(fun)

@spec custom((any() -> validation_result())) :: validator()

Wrap a one-argument function as the validator {:custom, fun}.

fun is called with the value and must return :ok or {:error, message}.

Examples

iex> validator = Drafter.Validation.custom(fn v -> if v > 0, do: :ok, else: {:error, "positive only"} end)
iex> Drafter.Validation.run_validator(1, validator)
:ok
iex> Drafter.Validation.run_validator(-1, validator)
{:error, "positive only"}

email(value)

@spec email(any()) :: validation_result()

Fails unless value is a binary of the form local@domain.tld, with no spaces or further @.

A non-binary fails. The two-argument form takes the failure message, default "Invalid email address".

Examples

iex> Drafter.Validation.email("a@b.co")
:ok

iex> Drafter.Validation.email("a@b")
{:error, "Invalid email address"}

iex> Drafter.Validation.email(nil, "Need an address")
{:error, "Need an address"}

email(value, message)

@spec email(any(), String.t()) :: validation_result()

max_length(value, max)

@spec max_length(any(), non_neg_integer()) :: validation_result()

Fails unless value is a binary of at most max graphemes.

A non-binary fails. The three-argument form takes the failure message, default "Must be at most <max> characters".

Examples

iex> Drafter.Validation.max_length("ab", 3)
:ok

iex> Drafter.Validation.max_length("abcd", 3)
{:error, "Must be at most 3 characters"}

iex> Drafter.Validation.max_length("abcd", 3, "Too long")
{:error, "Too long"}

max_length(value, max, message)

@spec max_length(any(), non_neg_integer(), String.t()) :: validation_result()

min_length(value, min)

@spec min_length(any(), non_neg_integer()) :: validation_result()

Fails unless value is a binary of at least min graphemes.

A non-binary fails. The three-argument form takes the failure message, default "Must be at least <min> characters".

Examples

iex> Drafter.Validation.min_length("abcde", 3)
:ok

iex> Drafter.Validation.min_length("ab", 3)
{:error, "Must be at least 3 characters"}

iex> Drafter.Validation.min_length(12, 3)
{:error, "Must be at least 3 characters"}

min_length(value, min, message)

@spec min_length(any(), non_neg_integer(), String.t()) :: validation_result()

pattern(value, regex)

@spec pattern(any(), Regex.t() | String.t()) :: validation_result()

Fails unless regex matches somewhere in value.

regex is a Regex or a source string, which is compiled and so raises Regex.CompileError if malformed. Anchor it yourself to require a full match. A non-binary value fails. The three-argument form takes the failure message, default "Invalid format".

Examples

iex> Drafter.Validation.pattern("abc123", "\\d+")
:ok

iex> Drafter.Validation.pattern("abc", ~r/^\d+$/)
{:error, "Invalid format"}

iex> Drafter.Validation.pattern("abc", ~r/^\d+$/, "Digits only")
{:error, "Digits only"}

pattern(value, regex, message)

@spec pattern(any(), Regex.t() | String.t(), String.t()) :: validation_result()

range(value, min, max)

@spec range(any(), number(), number()) :: validation_result()

Fails unless value is a number in min..max inclusive.

A non-number fails. The four-argument form takes the failure message, default "Must be between <min> and <max>".

Examples

iex> Drafter.Validation.range(5, 1, 10)
:ok

iex> Drafter.Validation.range(11, 1, 10)
{:error, "Must be between 1 and 10"}

iex> Drafter.Validation.range("5", 1, 10)
{:error, "Must be between 1 and 10"}

range(value, min, max, message)

@spec range(any(), number(), number(), String.t()) :: validation_result()

required(value)

@spec required(any()) :: validation_result()

Fails when value is nil, "" or []; anything else passes.

The two-argument form takes the failure message, default "This field is required". Note that 0, false and %{} all pass.

Examples

iex> Drafter.Validation.required("x")
:ok

iex> Drafter.Validation.required(nil)
{:error, "This field is required"}

iex> Drafter.Validation.required([], "Pick one")
{:error, "Pick one"}

iex> Drafter.Validation.required(false)
:ok

required(arg1, message)

@spec required(any(), String.t()) :: validation_result()

run_validator(value, validator)

@spec run_validator(any(), validator()) :: validation_result()

Run one validator against value.

Returns :ok or {:error, message}. Raises FunctionClauseError for a term that is not one of the validator forms listed in the module documentation.

Examples

iex> Drafter.Validation.run_validator("abc", {:min_length, 5})
{:error, "Must be at least 5 characters"}

iex> Drafter.Validation.run_validator("abc", {:min_length, 5, "Too short"})
{:error, "Too short"}

iex> Drafter.Validation.run_validator(7, {:range, 1, 10})
:ok

iex> Drafter.Validation.run_validator("x", fn v -> if v == "x", do: :ok, else: {:error, "no"} end)
:ok

validate(value, list)

@spec validate(any(), [validator()]) :: validation_result()

Run validators against value in order.

Returns :ok when all pass, or the first {:error, message} and stops there. An empty list is :ok.

Examples

iex> Drafter.Validation.validate("a@b.co", [:required, :email])
:ok

iex> Drafter.Validation.validate("nope", [:required, :email])
{:error, "Invalid email address"}

iex> Drafter.Validation.validate("", [{:required, "Name needed"}])
{:error, "Name needed"}

iex> Drafter.Validation.validate("anything", [])
:ok