|
| struct | morph::forms::FieldMeta |
| | Per-field presentation overrides and scalar bounds: label, help, placeholder, read-only, hidden, minimum/maximum/multipleOf, and a plain member's display unit/decimals (docs/spec/forms/forms.md, "Field metadata"). More...
|
| struct | morph::forms::Engaged< V, A > |
| | Condition: field is engaged (has a value). One of the closed condition kinds a requiredWhen / visibleWhen / readonlyWhen rule's when clause accepts. More...
|
| struct | morph::forms::NotEngaged< V, A > |
| | Condition: field is not engaged. The complement of engaged. More...
|
| struct | morph::forms::Greater< V, A > |
| | Rule/condition: *lhs > *rhs when both operands are engaged; vacuously satisfied when either is unengaged (a form still being filled in must not fail this comparison prematurely — see forms.md). Compares the operands' engaged values (operator*()) directly — e.g. the underlying math::Rational for Quantity, or DateTime for Timestamp — never the field type's own (possibly throwing, for Quantity) operator<=>. More...
|
| struct | morph::forms::GreaterOrEqual< V, A > |
| | Rule/condition: *lhs >= *rhs when both operands are engaged; vacuously satisfied when either is unengaged. See Greater for the exact-value / vacuous-operand rationale. More...
|
| struct | morph::forms::Less< V, A > |
| | Rule/condition: *lhs < *rhs when both operands are engaged; vacuously satisfied when either is unengaged. See Greater for the exact-value / vacuous-operand rationale. More...
|
| struct | morph::forms::LessOrEqual< V, A > |
| | Rule/condition: *lhs <= *rhs when both operands are engaged; vacuously satisfied when either is unengaged. See Greater for the exact-value / vacuous-operand rationale. More...
|
| struct | morph::forms::Equals< V, A, L > |
| | Condition: field's engaged value equals literal. An unengaged field is not vacuously satisfied here (unlike the comparison kinds) — a field with no value cannot equal anything, so equals returns false while the field is unengaged. More...
|
| struct | morph::forms::RequiredWhen< V, A, Cond > |
| | Rule: field must be engaged whenever Cond holds; vacuously satisfied (not required) while the condition does not hold. More...
|
| struct | morph::forms::ExactlyOneOf< A, Vs > |
| | Rule: exactly one of the listed fields is engaged. More...
|
| struct | morph::forms::AtLeastOneOf< A, Vs > |
| | Rule: at least one of the listed fields is engaged. More...
|
| struct | morph::forms::MutuallyExclusive< A, Vs > |
| | Rule: at most one of the listed fields is engaged. More...
|
| struct | morph::forms::VisibleWhen< V, A, Cond > |
| | Presentation rule: the listed field is shown only while Cond holds. Never gates allRulesSatisfied — while hidden, the field's current draft value still travels in the payload (hiding never clears it), exactly like a static x-hidden field. More...
|
| struct | morph::forms::ReadonlyWhen< V, A, Cond > |
| | Presentation rule: the listed field is editable only while Cond does not hold (the field is read-only while when holds). Never gates allRulesSatisfied, exactly like VisibleWhen. More...
|
| struct | morph::forms::And< A, Conds > |
| | Compound condition: all of Conds... hold. Nests to any depth — each Cond may itself be a leaf (Engaged, Equals, a comparison, …) or another And/Or/Not. Usable both as a nested when clause and directly as a top-level formRules entry (it declares isPresentation and test() exactly like every other validation rule), which is what lets a single rule carry a compound condition tree instead of factoring the composition into multiple single-condition rules. More...
|
| struct | morph::forms::Or< A, Conds > |
| | Compound condition: at least one of Conds... holds. Nests to any depth, and is usable directly as a top-level formRules entry, exactly like And. More...
|
| struct | morph::forms::Not< A, Cond > |
| | Compound condition: the nested condition does not hold. Nests to any depth, and is usable directly as a top-level formRules entry, exactly like And/Or. More...
|
| struct | morph::forms::RuleList< Rules > |
| | Composed list of an action's declared cross-field rules — the value of A::formRules. Built by ruleList(...); never constructed directly. More...
|
| struct | morph::forms::UnsatisfiableFormError |
| | Thrown by schemaJson<A>() when A's declared formRules contradict A's own derived required array so completely that no submission could satisfy both. More...
|
| struct | morph::forms::ConflictingOptionsError |
| | Thrown by schema generation when a Choice<…> member's FieldMeta also sets optionsAction: the member's type already names where its options come from, and two sources cannot both hold. More...
|
| struct | morph::forms::IncompleteOptionsError |
| | Thrown by schema generation when a FieldMeta sets optionsAction without both valueField and labelField, or either of those without optionsAction: options need the action, the value field and the label field together. More...
|
| struct | morph::forms::QuantityDecodeError |
| | Thrown when a decoded action has a Quantity field whose engaged value falls outside its unit's declared bounds (UnitTraits<E>::bounds). More...
|
|
template<typename V, typename A>
requires EngageableField<V> |
| constexpr auto | morph::forms::engaged (V A::*field) |
| | Builds an Engaged<V, A> condition testing whether field is engaged.
|
template<typename V, typename A>
requires EngageableField<V> |
| constexpr auto | morph::forms::notEngaged (V A::*field) |
| | Builds a NotEngaged<V, A> condition testing whether field is not engaged.
|
template<typename V, typename A>
requires detail::ComparableField<V> |
| constexpr auto | morph::forms::greater (V A::*lhs, V A::*rhs) |
| | Builds a Greater<V, A> rule/condition: *lhs > *rhs.
|
template<typename V, typename A>
requires detail::ComparableField<V> |
| constexpr auto | morph::forms::greaterOrEqual (V A::*lhs, V A::*rhs) |
| | Builds a GreaterOrEqual<V, A> rule/condition: *lhs >= *rhs.
|
template<typename V, typename A>
requires detail::ComparableField<V> |
| constexpr auto | morph::forms::less (V A::*lhs, V A::*rhs) |
| | Builds a Less<V, A> rule/condition: *lhs < *rhs.
|
template<typename V, typename A>
requires detail::ComparableField<V> |
| constexpr auto | morph::forms::lessOrEqual (V A::*lhs, V A::*rhs) |
| | Builds a LessOrEqual<V, A> rule/condition: *lhs <= *rhs.
|
template<typename V, typename A, RuleLiteral L>
requires ComparableAgainstLiteral<V, L> |
| constexpr auto | morph::forms::equals (V A::*field, L literal) |
| | Builds an Equals<V, A, L> condition: field's engaged value equals literal.
|
template<typename V, typename A, std::size_t N>
requires ComparableAgainstLiteral<V, detail::LiteralString<N>> |
| constexpr auto | morph::forms::equals (V A::*field, const char(&literal)[N]) |
| | equals overload for a string-literal argument (equals(&A::code, "X")), so callers do not have to spell std::string{"X"} explicitly.
|
template<typename V, typename A, Condition Cond>
requires EngageableField<V> && std::same_as<detail::ConditionActionType<Cond>, A> |
| constexpr auto | morph::forms::requiredWhen (V A::*field, Cond when) |
| | Builds a RequiredWhen<V, A, Cond> rule: field becomes required exactly when when holds.
|
template<typename A, typename... Vs>
requires (EngageableField<Vs> && ...) |
| constexpr auto | morph::forms::exactlyOneOf (Vs A::*... fields) |
| | Builds an ExactlyOneOf<A, Vs...> rule over fields.
|
template<typename A, typename... Vs>
requires (EngageableField<Vs> && ...) |
| constexpr auto | morph::forms::atLeastOneOf (Vs A::*... fields) |
| | Builds an AtLeastOneOf<A, Vs...> rule over fields.
|
template<typename A, typename... Vs>
requires (EngageableField<Vs> && ...) |
| constexpr auto | morph::forms::mutuallyExclusive (Vs A::*... fields) |
| | Builds a MutuallyExclusive<A, Vs...> rule over fields.
|
template<typename V, typename A, Condition Cond>
requires std::same_as<detail::ConditionActionType<Cond>, A> |
| constexpr auto | morph::forms::visibleWhen (V A::*field, Cond when) |
| | Builds a VisibleWhen<V, A, Cond> presentation rule: field is shown only while when holds.
|
template<typename V, typename A, Condition Cond>
requires std::same_as<detail::ConditionActionType<Cond>, A> |
| constexpr auto | morph::forms::readonlyWhen (V A::*field, Cond when) |
| | Builds a ReadonlyWhen<V, A, Cond> presentation rule: field is editable only while when does not hold.
|
template<Condition Cond0, Condition... Conds>
requires (std::same_as<detail::ConditionActionType<Cond0>, detail::ConditionActionType<Conds>> && ...) |
| constexpr auto | morph::forms::andOf (Cond0 condition0, Conds... conditions) |
| | Builds an And<A, Conds...> condition: every listed condition must hold.
|
template<Condition Cond0, Condition... Conds>
requires (std::same_as<detail::ConditionActionType<Cond0>, detail::ConditionActionType<Conds>> && ...) |
| constexpr auto | morph::forms::orOf (Cond0 condition0, Conds... conditions) |
| | Builds an Or<A, Conds...> condition: at least one listed condition must hold.
|
| template<Condition Cond> |
| constexpr auto | morph::forms::notOf (Cond condition) |
| | Builds a Not<A, Cond> condition: negates condition.
|
| template<typename... Rules> |
| constexpr auto | morph::forms::ruleList (Rules... rules) |
| | Composes rules into the RuleList an action assigns to its static constexpr formRules member.
|
| template<typename A> |
| constexpr bool | morph::forms::allRulesSatisfied (const A &action) noexcept |
| | Whether every validation rule in A::formRules holds for action. Presentation rules (visibleWhen / readonlyWhen) are skipped — they can never fail this check. Returns true unconditionally for an action with no formRules (safe to call from every action's validate() regardless of whether it declares rules).
|
| template<auto Dst, auto... Inputs, typename Fn> |
| consteval auto | morph::forms::computed (Fn fn) noexcept |
| | Builds one computed-field declaration binding a destination member to its declared input members and a pure derivation function.
|
| template<typename... Fields> |
| consteval auto | morph::forms::computeList (Fields... fields) noexcept |
| | Builds a ComputeList from one or more computed(...) declarations.
|
| template<typename A> |
| constexpr void | morph::forms::recomputeAll (A &action) |
| | Recomputes every entry of A::computedFields in place on action.
|
| template<auto MemberPtr> |
| FieldMeta | morph::forms::describe (std::string_view label={}, std::string_view help={}) noexcept |
| | Builds a FieldMeta for the member named by MemberPtr, so the wire key is never restated as a string.
|
| template<typename A> |
| constexpr void | morph::forms::reconcileDeclaredPrecision (A &action) |
| | Rounds every Quantity member of action to its declared precision, so the stored value matches the precision the schema advertises via x-decimalPlaces.
|
| template<typename A> |
| std::optional< std::string > | morph::forms::checkQuantityBounds (const A &action) |
| | Checks every Quantity member of action against its unit's declared bounds (morph::units::Quantity::withinDeclaredBounds, driven by the optional UnitTraits<E>::bounds(E) customisation point).
|
| template<typename A> |
| void | morph::forms::enforceQuantityBounds (const A &action) |
| | Runs checkQuantityBounds<A>(action) and throws QuantityDecodeError naming the first out-of-bounds field, if any. The throwing counterpart used directly on the decode path (registry.hpp/bridge.hpp call sites); a caller that wants the field name without an exception uses checkQuantityBounds itself.
|
| template<typename A> |
| constexpr bool | morph::forms::allRequiredEngaged (const A &action) noexcept |
| | Whether every required empty-capable member of action is engaged (has a value).
|
| template<typename A> |
| bool | morph::forms::allFieldBoundsSatisfied (const A &action) noexcept |
| | Whether every numeric member of action satisfies the minimum/maximum/multipleOf its A::fieldMetadata entry declares.
|
| template<typename A> |
| const std::string & | morph::forms::schemaJson () |
| | Generates the JSON Schema for action type A, ready for a client-side form renderer.
|
JSON-Forms-style schema generation for morph actions.
Given an action type A (a plain aggregate, as registered with BRIDGE_REGISTER_ACTION), this header produces a standard JSON Schema a client can render a form from at runtime — the "describe" half of an auto-generated GUI. It builds on glaze's write_json_schema (which already contributes types, $defs, per-field metadata declared via glz::json_schema<A>, and the ExtUnits stamped by morph::units::Quantity) and closes the gaps glaze leaves open. The list below is the main surface, not an exhaustive enumeration of every key emitted:
- required — under the options schemaJson<A>() uses, glaze derives no required entries from member types: glz::requires_key only returns true for a member when meta<T>::requires_key says so or when Opts.error_on_missing_keys is set, and morph sets neither. (A type declaring meta<V>::required, and a tagged variant's discriminator, do still get one.) schemaJson<A>() always writes its own, overwriting whatever the schema writer did or did not produce: a member is required unless it is a std::optional<...> or its name is listed in the action's opt-out list (see below).
- x-decimalPlaces — for Quantity members, the field's declared precision (Quantity<U, Dec>::declaredDecimals, which defaults to the unit's UnitTraits default but is overridable per member), so a client knows the input step without hardcoding unit knowledge.
- x-order — the member's declaration index on every property, so a renderer can lay fields out in declaration order (JSON object key order is not reliable once schemas pass through DOMs/maps).
- x-unitAlternatives — for Quantity members whose unit system declares convertible units (UnitTraits<E>::relations): an array of {id, display, decimals, num, den} entries, where num/den is the exact alternative-to-canonical ratio. Renderers offer a unit selector and recalculate entered values exactly on switch; payloads always carry the canonical unit.
- x-optionsAction / x-optionValue / x-optionLabel — for morph::forms::Choice members: which registered action serves the options, and which result-row fields carry the submitted value and the display label. Renderers turn these into combo boxes populated by executing the named action.
- x-optionsDependsOn — for a Choice whose options are parameterised by sibling field values (Choice's DependsOn pack): the wire names of those sibling fields. Renderers send {name: value, ...} as the options-action request body instead of an empty one, and re-fetch when a named field changes. Omitted when the Choice declares no dependency.
- x-layout / x-group / x-section / x-colspan — for actions declaring a static constexpr formLayout and/or fieldSpans (morph::forms::FieldGroup / FieldSpan, forms/layout.hpp): visual structure (sections, tabs, an accordion) over the flat field list, and per-field grid column spans. Absent either declaration, none of these keys are emitted and a renderer lays fields out exactly as it does today.
- x-widget — for a field whose type declares a noexcept static
constexpr widget() (Multiline, Ranged — widget_hints.hpp), or any field named in a fieldMetadata-shaped override: the renderer's preferred control id ("textarea", "slider", "radio", …).
- x-min / x-max / x-step — for a field whose type additionally declares min()/max()/step() (the Ranged shape): the slider's control-track bounds and increment — advisory, not a validation bound.
- x-rules — for an action declaring a static constexpr formRules (morph::forms::ruleList(...)): a closed, typed cross-field rule vocabulary (requiredWhen, comparisons, membership, presentation, and the compound andOf/orOf/notOf conditions that nest a condition tree to any depth) evaluated identically by the schema, the client, and the server. See morph::forms::allRulesSatisfied below and docs/spec/forms/forms.md.
- minimum / maximum / multipleOf — for a field whose fieldMetadata entry declares them (morph::forms::FieldMeta): a per-field scalar bound, stamped on the property node as the standard JSON-Schema keys of those names. multipleOf: 1 is how "whole number" is spelled. morph::forms::allFieldBoundsSatisfied evaluates the same declaration in C++, so an action's validate() enforces exactly what the client was served. Bounds are per field, unlike UnitTraits::bounds, which is per unit.
- ExtUnits / x-displayDecimals — for a non-Quantity field whose FieldMeta declares unit / decimals: the display unit (in the same ExtUnits shape a Quantity carries) and the fraction digits a renderer shows and accepts. Presentation only — the member keeps its plain JSON encoding.
- x-computed / x-readonly — for a member listed as the destination of an action's computedFields declaration: the field is derived from sibling inputs (named in x-computed.inputs) and must not be rendered as an editable control (x-readonly: true). See morph::forms::computed, morph::forms::computeList, and morph::forms::recomputeAll.
morph::time::Timestamp members need no extension keys: their schema carries the standard "format": "date-time" annotation.
Nested aggregates (recursive, cycle-safe). A member whose type is itself a reflectable aggregate — a plain nested struct, or std::vector<Sub> — gets its own members annotated too: x-order, title/FieldMeta, required, and the Quantity/Choice/widget/ ranged-bounds rules above, applied against the nested type's own reflection. Unlike the top level, this recurses into the type graph, to whatever depth it has: there is no depth limit, and a self- or mutually-referential type is described rather than rejected — glaze emits a finite $ref-cyclic schema for it, and the runtime $defs visited set walks that once. Computed fields/formLayout/fieldSpans/formRules remain top-level-only regardless of depth. See docs/spec/forms/forms.md, "Nested aggregates (recursive, cycle-safe)", and detail::annotateNestedAggregateRef.
- Declaring optional fields
- Required is the default. An action opts individual fields out with a static member list:
struct RecordMeasurement {
std::int64_t sampleId = 0;
Density density{};
Moisture moisture{};
static constexpr std::array optionalFields{std::string_view{"moisture"}};
};
std::expected< TableQuery, TableError > validate(TableQuery query, std::span< ColumnInfo const > columns, QueryLimits const &limits={}, std::function< bool(std::string_view)> const &mayRead={})
Checks query against the table's columns and limits.
- Declaring computed fields
- A derived, read-only field is declared with a static constexpr map from a destination member to its declared input members and a pure derivation:
struct LineItem {
Quantity<Units, 2> qty;
Quantity<Units, 2> price;
Quantity<Units, 2> total;
[](const auto& s) { return s.qty * s.price; }));
};
schemaJson<A>() then emits x-computed/x-readonly on total and excludes it from required; recomputeAll<A>(action) is the single evaluator the reactive set<> path and every dispatch path call to overwrite it authoritatively – see bridge.md/registry.md.
- Readiness helper
- allRequiredEngaged(action) returns true when every required empty-capable member (Quantity, Choice, Timestamp — anything with a hasValue()) is engaged. Wire it up as the action's validate() (the existing ActionValidator machinery picks that method up automatically) so the same declaration drives the schema's required array, the client-side submit gate, and the fielded-action readiness check. Non-quantity members are not checked — a plain int64_t cannot express "not filled in"; use a Quantity (or a custom validate()) when that distinction matters. That exclusion covers std::string too: a std::string member exposes empty(), not hasValue(), so a "this text
field must be non-empty" rule is a custom validate() body, never something this helper reports on.
Its walk is flat, unlike the schema generation described above: it inspects the action's own top-level members and does not descend into a nested aggregate, a std::vector of one, or a std::optional holding one. A nested type's own required fields are therefore not gated by an outer allRequiredEngaged — delegating to the nested value's validate() is the caller's to write.
Definition in file forms.hpp.