Swap a validate_number/3 bound between its strict and non-strict form — the changeset
counterpart of the in-query :comparison swap. validate_number(:age, greater_than: 0) →
validate_number(:age, greater_than_or_equal_to: 0). "Is the bound itself tested?" — the two
validations accept the same values except one exactly on the bound, so the mutant survives
unless a test builds a changeset whose value equals it and asserts the validation result. Family
:validation_boundary; equivalence-sensitive (Mutare.Ecto.Equivalence), because a kill needs
exactly that fixture.
Four swaps, in two pairs: greater_than ↔ greater_than_or_equal_to and less_than ↔
less_than_or_equal_to. equal_to/not_equal_to are deliberately not swapped: the mutant
rejects the one value the written validation accepts (or the reverse), which any happy-path test
kills — no signal beyond the whole-call :validation_drop. validate_length's min/max/is
are inclusive with no strict counterpart, so core's integer family provides their off-by-one
mutations (the value stays an ordinary expression — Mutare.Ecto.Changeset.Routing).
One mutant per swappable option; every other option (message:, a second bound) is kept as
written. Matched like the stage drops (Mutare.Ecto.Changeset): the call resolves to
Ecto.Changeset in any spelling, and the options are the trailing argument in the direct and
piped forms alike. A non-keyword options argument (validate_number(cs, :age, opts)) yields
nothing — there is no written key to swap. Delivered in place (the call rebuilt in its written
form), reported at and tagged with the written key so
# mutare:ignore[ecto:greater_than] on a bound's line suppresses its swap.
Summary
Functions
The finer # mutare:ignore label for a bound swap: the written key
(# mutare:ignore[ecto:greater_than] leaves a greater_than: bound's swap alone). Total over
any key; swap/1 returns the replacement key or nil when no swap exists.
Bound-swap mutations for a validate_number/3 call as :validation_boundary tags, or [] for
any other call, for a call without a written keyword options list, or for options without a
swappable key.
The strict/non-strict counterpart of a validate_number option key, or nil for any other key.
Functions
The finer # mutare:ignore label for a bound swap: the written key
(# mutare:ignore[ecto:greater_than] leaves a greater_than: bound's swap alone). Total over
any key; swap/1 returns the replacement key or nil when no swap exists.
@spec mutations(Macro.t(), Mutare.Ecto.Context.t()) :: [Mutare.Ecto.Tag.t()]
Bound-swap mutations for a validate_number/3 call as :validation_boundary tags, or [] for
any other call, for a call without a written keyword options list, or for options without a
swappable key.
The strict/non-strict counterpart of a validate_number option key, or nil for any other key.