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

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.

The field position is the second argument in both spellings: core hands a pipe stage over as the direct call it is sugar for, so cs |> validate_length(:name, …) is classified as validate_length(cs, :name, …), with the changeset at position 0 (routed :expression, so the upstream pipeline stays mutable).

Summary

Functions

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

Per-argument treatment for a changeset stage name with arguments args (the piped changeset included, at position 0): 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)

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

treatments(name, args)

@spec treatments(atom(), [Macro.t()]) :: [Mutare.CallRouting.treatment()]

Per-argument treatment for a changeset stage name with arguments args (the piped changeset included, at position 0): 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.