Lightning.Validators (Lightning v2.19.0-pre)
View SourceExtra validators for Ecto.Changeset.
Summary
Functions
True when a string is made up entirely of characters that draw nothing.
Folds a name to the form used when matching it against a stored one.
Returns true when value is a well-formed UUID that will dump cleanly to a
:binary_id on insert/update.
Validates that an email field contains a properly formatted email address.
Validate that only one of the fields is set at a time.
Normalises a name field and rejects any control character in it.
Rejects a name that will not fit the column it is stored in.
Rejects a NUL in a field that ends up inside a jsonb column.
Rejects a NUL anywhere inside a map field that ends up in a jsonb column.
Validate that at least one of the fields is set.
Validate that an association is present
Validates a URL in a changeset field.
Validates that the given field(s) contain a well-formed UUID.
Functions
True when a string is made up entirely of characters that draw nothing.
Exposed so the fixture that keeps the client's copy of this rule in step can
be generated from it. See assets/js/utils/nameValidation.ts.
Folds a name to the form used when matching it against a stored one.
Only NFC and a trim, which is what validate_name/3 applies on save, so a
caller comparing a name that never went through a changeset against one that
did is comparing like with like. Not case folding: names are case-sensitive.
Returns true when value is a well-formed UUID that will dump cleanly to a
:binary_id on insert/update.
Uses Ecto.UUID.dump/1 (not cast/1): dump rejects raw 16-byte binaries and
unsubstituted import placeholders that cast would accept, matching what the
database actually enforces. nil is not a valid UUID.
This is the single source of truth for the "dumpable UUID" check — both
validate_uuid/2 and schema-level guards (e.g. Workflows.Job) build on it so
they cannot drift apart.
@spec validate_email_format(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
Validates that an email field contains a properly formatted email address.
Applies: required check, format regex, max 160 chars, lowercases the value.
This is a pure format check — no database lookup. Use User.validate_email/1
when you also need to verify the email is unique in the users table.
@spec validate_exclusive(Ecto.Changeset.t(), [atom()], String.t()) :: Ecto.Changeset.t()
Validate that only one of the fields is set at a time.
Example:
changeset
|> validate_exclusive(
[:source_job_id, :source_trigger_id],
"source_job_id and source_trigger_id are mutually exclusive"
)
@spec validate_name(Ecto.Changeset.t(), atom(), String.t()) :: Ecto.Changeset.t()
Normalises a name field and rejects any control character in it.
The value is normalised to NFC and trimmed before anything else runs, so a
later validate_required/3 or validate_length/3 in the same changeset sees
the value that will actually be stored. Call this straight after cast/3.
assets/js/utils/nameValidation.ts is the client-side copy of the rule.
@spec validate_name_fits_column( Ecto.Changeset.t(), atom(), String.t(), pos_integer() ) :: Ecto.Changeset.t()
Rejects a name that will not fit the column it is stored in.
Postgres counts a varchar in codepoints; the product caps above this one
count graphemes, so a name built from multi-codepoint clusters can pass a
100 grapheme cap and raise 22001 on insert.
Skipped when the field already has an error, so a plainly over-long name gets
the product cap's message and this one stays quiet. width defaults to 255,
the width of every name column in this schema; pass it for a narrower one
such as credentials.schema.
The message callers pass should not quote a number: from where the user sits the limit is the product cap.
@spec validate_no_null_bytes(Ecto.Changeset.t(), atom(), String.t()) :: Ecto.Changeset.t()
Rejects a NUL in a field that ends up inside a jsonb column.
Postgres refuses a NUL anywhere inside a jsonb value (22P05). Unlike a
name, these fields legitimately hold newlines and tabs, so only the NUL is
refused rather than the whole control set. Malformed UTF-8 goes the same way.
@spec validate_no_null_bytes_deep(Ecto.Changeset.t(), atom(), String.t()) :: Ecto.Changeset.t()
Rejects a NUL anywhere inside a map field that ends up in a jsonb column.
Walks the whole structure rather than checking the top level, because a NUL in a key is just as fatal as one in a value.
@spec validate_one_required(Ecto.Changeset.t(), [atom()], String.t()) :: Ecto.Changeset.t()
Validate that at least one of the fields is set.
@spec validate_required_assoc(Ecto.Changeset.t(), atom(), String.t()) :: Ecto.Changeset.t()
Validate that an association is present
NOTE This should only be used when using
put_assoc, notcast_assoc.cast_assocprovides arequired: trueoption. Unlikevalidate_required, this does not add the field to therequiredlist in the schema.
@spec validate_url(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
Validates a URL in a changeset field.
Ensures that the URL:
- Has a valid
httporhttpsscheme. - Has a valid host (domain name, IPv4, or IPv6).
- The host is not blank and does not exceed 255 characters.
Returns a changeset error for invalid URLs.
@spec validate_uuid(Ecto.Changeset.t(), atom() | [atom()]) :: Ecto.Changeset.t()
Validates that the given field(s) contain a well-formed UUID.
:binary_id fields are not format-checked by cast/3 — a malformed value
(e.g. an unsubstituted import placeholder) passes casting and only raises
Ecto.ChangeError when dumped on insert/update. This converts that into a
changeset error instead.
Only runs when a non-nil change is present for the field, so optional foreign keys left unset are unaffected.
Narrowing: uses
Ecto.UUID.dump/1, notcast/1.dumpadditionally rejects raw 16-byte binaries and unsubstituted placeholders thatcastaccepted. Confirmed no live caller relied on the laxer behaviour (uppercase canonical UUIDs still pass).
changeset
|> validate_uuid([:id, :workflow_id])