Gate
Gate — changeset-style form validation for Bastion.
A Gate is an immutable value that tracks:
- The proposed changes (permitted fields extracted from raw params)
- Validation errors accumulated along the pipeline
- The original record data (for cast_record / update flows)
Gates flow through a validation pipeline using the pipe operator:
fn registration_gate(params) do
Gate.cast(params, ["email", "password"])
|> Gate.validate_required(["email", "password"])
|> Gate.validate_format("email", ".+@.+\\..+")
|> Gate.validate_length("password", [LenMin(12), LenMax(72)])
endCheck validity and read errors:
if Gate.valid?(gate) do
-- apply to DB
else
render_form(conn, gate) -- errors show in template
endThe gate is valid when it has no errors. A gate with errors is still a complete value — you pass it back to the template to display inline errors.
unique_constraint/3 is a marker only: the DB layer checks uniqueness on insert and merges errors back into the gate via Gate.add_error/3.
Types
Functions
Add a validation error for a field. Accumulated — calling multiple times for the same field adds multiple errors, but error_for/2 returns the first.
Gate.add_error(gate, "email", "has already been taken")Build a gate from raw params, keeping only the permitted fields.
`params` is a list of (key, value) string pairs — e.g., from
`Request.body_params/1`. Only fields named in `permitted` are
kept as changes; all others are dropped.
Gate.cast([("email", "[email protected]"), ("role", "admin")], ["email"])
-- Gate with changes: [("email", "[email protected]")]
-- "role" dropped — not in permitted listUpdate an existing record's fields from params, keeping only permitted fields.
Same as cast/2 but stores the original record data so error messages and
the template can show existing values for unchanged fields.
let gate = Gate.cast_record(user, params, ["email"])
-- changes = only the permitted fields that were in params
-- original_data = user's existing fieldsRemove a change from the gate.
Gate.delete_change(gate, "password")Returns the first error message for a field, or None.
Gate.error_for(gate, "email") -- Some("has invalid format") or NoneReturns all validation errors as a list of (field, message) pairs.
Gate.errors(gate)
-- [("email", "has invalid format"), ("password", "is too short ...")]Returns the proposed change value for a field, if present.
None if the field was not in the permitted list or not in the params.
Gate.get_change(gate, "email") -- Some("[email protected]") or NoneReturns the value for a field: the change if present, else the original data.
Use this in templates to populate form fields — shows the user's input
on re-render, falling back to the original record value.
Gate.get_field(gate, "email")Add or replace a change for a field.
Used in validation pipeline helpers (e.g., hash_password replaces
the raw "password" change with "hashed_password").
Gate.put_change(gate, "hashed_password", hashed)Mark a field with a unique constraint that will be checked by the DB layer.
This is a no-op in the gate itself — it records the constraint metadata
in the errors list as a special sentinel so that the DB layer (Depot) can
map a uniqueness violation back to a field-level error message.
`opts` may include ConstraintName(str) and ConstraintMessage(str).
Gate.unique_constraint(gate, "email",
[ConstraintName("users_email_index"),
ConstraintMessage("has already been taken")])Returns true if the gate has no validation errors.
if Gate.valid?(gate) do Depot.insert(db, schema, gate) endValidate that field matches field_confirmation.
Looks for a parallel change key `field ++ "_confirmation"`. If the
confirmation key is not present, no error is added (treat as not filled).
Gate.validate_confirmation(gate, "password")
-- checks "password" == "password_confirmation"Validate that a field value matches a regex pattern string.
Gate.validate_format(gate, "email", ".+@.+\\..+")Validate that a field value is one of the allowed values.
Gate.validate_inclusion(gate, "role", ["admin", "member", "viewer"])Validate the byte length of a field value.
Accepts a list of LenMin(n) and/or LenMax(n) constraints.
Gate.validate_length(gate, "password", [LenMin(12), LenMax(72)])
Gate.validate_length(gate, "username", [LenMin(3), LenMax(20)])Validate that a numeric string field falls within bounds.
Parses the field value as an integer before comparing.
Gate.validate_number(gate, "age", [NumMin(18), NumMax(120)])Require that all listed fields are present and non-empty.
Gate.validate_required(gate, ["email", "password"])