The per-argument routing classifier for the Ecto.Changeset pipeline stages the plugin mutates
(Mutare.Ecto.Changeset.stages/0 — every validator, constraint, and Repo-time hook it can
drop) — the changeset counterpart of the query classifier Mutare.Ecto.Host.Routing. Nothing
here is hosted; the classifier only holds back from core's families the positions where a
core swap would be a crash, not a mutant, so a run that lists the plugin next to :all
reports no crasher in a changeset stage. The test of each pin is Ecto's own handling of the
swapped value:
- A written field atom in the field position (
validate_length(cs, :name, …),unique_constraint(cs, :email)) names a column — structural, likefield/2's name in a query — and routes:raw. Core's atom family would swap it for:mutare, an unknown field Ecto raises on. Anything else in that slot — a field list (validate_required(cs, [:name, :email])), a variable, aprepare_changesfunction — routes:expression, so a list stays reachable for core's list families. - The option keys of
validate_numberroute per-pair{:keyword, …}, which leaves every key raw while the values stay:expression. Ecto rejects unsupported options (ArgumentError: unknown option), so a swapped key raises — every written key is raw, no key set is consulted. The bound literal keeps core's off-by-one mutants, andMutare.Ecto.ValidationBoundaryswaps strict and non-strict keys. validate_length's keys are not held back: Ecto readscount:/is:/min:/max:and ignores anything else, so core'smin:→mutare:is a live mutant — that one bound gone, the call otherwise intact — a more specific mutation than the whole-call:validation_drop. Only a written mode atom in itscount:value (:graphemes/:codepoints/:bytes) is held back, by a keyed refinement ([:expression, count: :raw])::mutareis no mode, and Ecto's mode dispatch has no clause for it on a string field (aCaseClauseError). A computed value stays:expression, so core can mutate how the mode is selected. The options of every other stage (a constraint'sname:, amessage:alone) stay:expression— the plugin defines no option restrictions for unlisted stages.
Which slot is the field position follows the call's form, as in the query classifier:
written directly (validate_length(cs, :name, …)) the changeset is the first visible argument
and the field the second; piped (cs |> validate_length(:name, …)) the changeset is the hidden
|> left side (routed :expression by core's default, so the upstream pipeline stays mutable)
and the field is the first visible argument.
Summary
Functions
Mutare.CallRouting.route_arguments/2 for a :routing-registered Ecto.Changeset stage:
the per-visible-argument treatments/3 classification, wrapped as ArgumentRoutes.
Per-visible-argument treatment for a changeset stage name with visible args in
pipe_mode: every position :expression, except a written field atom in the field position
(:raw), validate_number's trailing keyword list ({:keyword, …}, every key raw), and
validate_length's when its count: value is a written atom ([:expression, count: :raw]) —
the moduledoc.
Functions
@spec route_arguments( Mutare.CallRouting.Call.t(), Mutare.CallRouting.routing_context() ) :: Mutare.CallRouting.ArgumentRoutes.t()
Mutare.CallRouting.route_arguments/2 for a :routing-registered Ecto.Changeset stage:
the per-visible-argument treatments/3 classification, wrapped as ArgumentRoutes.
@spec treatments(atom(), [Macro.t()], Mutare.Mutator.pipe_mode()) :: [ Mutare.CallRouting.treatment() ]
Per-visible-argument treatment for a changeset stage name with visible args in
pipe_mode: every position :expression, except a written field atom in the field position
(:raw), validate_number's trailing keyword list ({:keyword, …}, every key raw), and
validate_length's when its count: value is a written atom ([:expression, count: :raw]) —
the moduledoc.