conditional_field
Reference: usage-rules/conditional.md.
Anatomy
conditional_field :owner, any() do
field :owner, struct(), struct: Person, validator: {Checks, :is_map_data}
field :owner, String.t(), validator: {Checks, :is_string_data},
derives: "validate(url)"
end
- Every child shares the parent's
:name. - Each child must declare
validator: {Mod, :fn}. - The MFA is called as
Mod.fn(field_name, value)and returns either{:ok, name, value}(this child wins) or{:error, name, reason}(try next).
Modifiers
| Modifier | Effect |
|---|---|
priority: true on one child |
Short-circuit when this child wins. ≤ 1 per parent. |
structs: true on a child or inner conditional |
Iterate a list; apply children element-wise. |
hint: "label" on a child |
Propagated into error maps as __hint__: for debugging. |
validator: {VAL, :is_map_data} on an inner conditional |
Gate descent: only enter when input is a map. |
Error aggregation
When no child matches, the parent emits:
%{
field: :owner,
action: :conditionals,
errors: [
%{field: :owner, action: :validator, __hint__: "as-map", message: "It is not map"},
%{field: :owner, action: :validator, __hint__: "as-string", message: "It is not string"}
]
}
Nested conditionals add one more :conditionals aggregator per level.
Descent semantics — common pitfall
The runtime feeds the same value to every child. So:
conditional_field :choice, any() do
field :choice, String.t(), validator: {Checks, :is_string_data}
conditional_field :choice, any(), validator: {Checks, :is_map_data} do
field :choice, :integer, validator: {Checks, :is_int_data}
end
end
The inner :integer child is unreachable: entering the nested conditional
requires is_map_data to succeed (value is a map), but the integer matcher
needs an integer. Same value flows through both levels.
To reach the integer: drop the map gate, or use structs: true to iterate
a list of mixed-type elements, or restructure with sub_field.
Nested depth
Works to arbitrary depth (closes issues #7, #8, #25 from the legacy 0.0.x
parser). Each level pattern-matches as a separate conditional_field entity
with its own children.
Test patterns
Property-style: assert that one :conditionals aggregator appears at the
expected level, and that the inner __hint__ set covers every child you
expect to have been tried. Avoid exact-shape = matches — child evaluation
order is documented but tests should be order-independent.