Mutare.Ecto.Changeset.Routing (mutare_ecto v0.2.1)

Copy Markdown View Source

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, like field/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, a prepare_changes function — routes :expression, so a list stays reachable for core's list families.
  • The option keys of validate_number route 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, and Mutare.Ecto.ValidationBoundary swaps strict and non-strict keys.
  • validate_length's keys are not held back: Ecto reads count:/is:/min:/max: and ignores anything else, so core's min: → 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 its count: value (:graphemes/:codepoints/:bytes) is held back, by a keyed refinement ([:expression, count: :raw]): :mutare is no mode, and Ecto's mode dispatch has no clause for it on a string field (a CaseClauseError). A computed value stays :expression, so core can mutate how the mode is selected. The options of every other stage (a constraint's name:, a message: 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

route_arguments(call, context)

Mutare.CallRouting.route_arguments/2 for a :routing-registered Ecto.Changeset stage: the per-visible-argument treatments/3 classification, wrapped as ArgumentRoutes.

treatments(name, args, pipe_mode)

@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.