Validate input without building a struct
v0.1.4
You do not always need a whole struct. GuardedStruct.Validate runs the rules you already declared against one value, one field or a few fields. Use it to check a single form field as the user types, to validate the fields of a PATCH request, or to test a derive string without a module. This guide shows the three functions.
Before you begin
The examples use this module. Fields have derive strings, and parent_email depends on account_type through on:.
defmodule Person do
use GuardedStruct
guardedstruct do
field :name, String.t(), enforce: true, derives: "sanitize(trim) validate(string, max_len=80)"
field :age, integer(), derives: "validate(integer, min_len=0, max_len=120)"
field :email, String.t(), derives: "sanitize(trim, downcase) validate(email_r)"
field :account_type, String.t(), derives: "validate(enum=String[personal::business])"
field :parent_email, String.t(), on: "root::account_type", derives: "validate(email_r)"
end
endCheck a value against a derive string
Validate.run/2 takes a derive string and a value. No module is involved. It returns {:ok, value} with the sanitized value, or {:error, errors}. The errors use the field name :__value__.
alias GuardedStruct.Validate
Validate.run("sanitize(trim, downcase) validate(string, email_r)", " [email protected] ")
# {:ok, "[email protected]"}
Validate.run("validate(integer, min_len=0)", -5)
# {:error,
# [
# %{
# field: :__value__,
# action: :min_len,
# message: "The minimum number the __value__ field is 0 and you have sent less than this number of entries"
# }
# ]}
Validate.run("", "x")
# {:ok, "x"}
Validate.run("validate(max_len=foo)", "x")
# {:error,
# [
# %{
# field: :__value__,
# action: :bad_parameters,
# message: "invalid parameter for `max_len` on field :__value__: expected non-negative integer, got \"foo\"."
# }
# ]}The string is checked the way a module checks a derives: string when it compiles. A string the parser cannot read, such as validate(enum=Atom[a, b]), a group named something other than sanitize or validate, such as validte(string), a group with no closing ), and an operand of the wrong type, such as max_len=foo or equal=Integer::abc, return an error with the action bad_parameters. They are never skipped and never raise. An empty or blank string checks nothing.
Check one field of a module
Validate.field/3 and Validate.field/4 take the module, the field name and the value. They run the field's derive string and its validator:. An unknown field returns an unknown_field error.
Validate.field(Person, :email, " [email protected] ")
# {:ok, "[email protected]"}
Validate.field(Person, :email, "nope")
# {:error, [%{field: :email, action: :email_r, message: "Incorrect email in the email field."}]}
Validate.field(Person, :zzz, "nope")
# {:error, [%{field: :zzz, action: :unknown_field, message: "field :zzz is not defined on Person"}]}Provide the fields a rule depends on
By default field/4 honors the on: and domain: options. A field with on: fails with dependent_keys unless you supply the field it depends on in context:. Pass mode: :isolated to skip those rules and run only the derive string and the validator.
Validate.field(Person, :parent_email, "[email protected]")
# {:error, [%{field: :parent_email, action: :dependent_keys, message: "The required dependency for field parent_email has not been submitted.\nYou must have field account_type in your input\n"}]}
Validate.field(Person, :parent_email, "[email protected]", context: %{account_type: "personal"})
# {:ok, "[email protected]"}
Validate.field(Person, :parent_email, "[email protected]", mode: :isolated)
# {:ok, "[email protected]"}Check some fields together
Validate.partial/2 takes a map and validates only the keys it contains. Missing fields are skipped, and the required-field check does not run. Cross-field rules resolve from the same map. String keys are accepted.
Validate.partial(Person, %{email: " [email protected] ", age: 30})
# {:ok, %{age: 30, email: "[email protected]"}}
Validate.partial(Person, %{email: "bad", age: -1})
# {:error,
# [
# %{field: :age, action: :min_len, message: "The minimum number the age field is 0 and you have sent less than this number of entries"},
# %{field: :email, action: :email_r, message: "Incorrect email in the email field."}
# ]}
Validate.partial(Person, %{})
# {:ok, %{}}A value that is not a map returns {:error, [%{field: :__value__, action: :bad_parameters, message: "input must be a map"}]}.
Know what these functions skip
- They do not build a struct, run
auto, applyfromor callmain_validator. Validate.field/3,4andValidate.partial/2use the derive extensions and thevalidate_deriveandsanitize_derivemodules of the module, asbuilder/1does.- They do not check
enforce. Usebuilder/1when a missing key must be an error. - On a
sub_field,field/3returns the value you pass without building the nested struct.
Next steps
- Error shapes and actions: what each error map contains.
- API reference: the signatures of
Validateand the other modules. - Derive string syntax: what you can write in a derive string.