Go API
package conformance // import "github.com/polyspec/crudui/packages/validator/go/testsupport/conformance"
Package conformance records conformance evidence for Go tests.
A test that runs a shared fixture case records the feature it proves,
the fixture, the runtime and the case. Nothing is written unless the
environment variable CRUDUI_CONFORMANCE_EVIDENCE names a directory;
scripts/check-conformance.mts reads it. The format matches
tests/conformance/evidence.mts.
FUNCTIONS
func Record(t testing.TB, feature, fixture, name string)
Record registers a cleanup on t that records whether the fixture case name
passed. Call it at the start of the subtest that runs exactly that case,
so the result reflects the case and every subtest it runs. Empty arguments
fail the test.
package validator // import "github.com/polyspec/crudui/packages/validator/go/validator"
Package model is the canonical (SPEC-mechanized, single-truth) Go model of the
crudui field.
The model encodes SPEC §3 exactly:
- Four role slots (validate / design / behavior / options). Each is
polymorphic across three shapes: false | {} | true. false cancels a composed
inheritance; {} carries the slot's sub-keys; true is the bare opt-in.
Every shape survives a round trip (custom UnmarshalJSON).
- Dependency isolation. A dependent key lives next to its target, never at
top level: scalar-type dependents go in options; multiple / lang / items
dependents go under that key. The structure keys multiple / lang are
themselves polymorphic false | {} | true.
- design node map. design addresses appearance per DOM node by key
(show / class / style / attributes / layout / label.{class,style}
/ wrapper.{class,style,attributes} / group.{class,style} /
prepend.{class,style}) — R8: which node a style targets is visible in the
key, not inferred.
- condition maps. A declaration-ordered map whose default key is the
literal true (R4 — no convention sigils such as _). Keys are evaluated in
declaration order; the first truthy key wins; order is preserved on a round
trip.
- composition. $ref (base inheritance) expands first, then $patch (add /
remove / replace).
Only canonical model keys are recognized. Other spellings (multiple_max,
lang:append, sortable*, add_buttons, …) and magic tokens (`*`, `:`) are NOT
valid keys here; they have no field on any struct and are rejected (R2 / R4).
Buckets are closed or open, as in the JSON schema. The closed buckets design,
design.{label,wrapper,group,prepend}, behavior, multiple and lang reject any key
they do not model on Unmarshal, and validate and messages accept the registered
rule names only (RuleNames). The open bucket options (Options.Extra) keeps
unknown keys.
Forbidden meta keys are rejected GLOBALLY, not only at top level: every open
bucket guards against them on Unmarshal, one level under the bucket too.
A schema layer expresses the same with propertyNames:{not:{enum:[…]}} rather
than additionalProperties:true, so extension is allowed but forbidden keys are
blocked everywhere.
x{key} comment keys (x-prefixed) are rejected here. The premise is that a
meta-schema x-strips them first, then validates the canonical spec; the strip is
the documented reason a raw x{key} never reaches this model.
CONSTANTS
const ConditionDefaultKey = "true"
ConditionDefaultKey is the only allowed default key of a condition map:
the literal true. Convention sigils (e.g. "_") are an R4 violation.
const ForbiddenKeyPrefix = "x"
ForbiddenKeyPrefix is the x{key} comment prefix. A meta-schema x-strips
such keys before validating the canonical spec; the strip is the documented
reason a raw x-prefixed key never legitimately reaches this model, so any
survivor is rejected. (xclass / xstyle are also listed literally above for
clarity.)
VARIABLES
var CompositionDirectives = []string{"$ref", "$patch"}
CompositionDirectives is the closed set of recognized composition keys.
var DesignNodeMap = []string{
"show",
"class",
"style",
"attributes",
"label.class",
"label.style",
"wrapper.class",
"wrapper.style",
"wrapper.attributes",
"group.class",
"group.style",
"prepend.class",
"prepend.style",
}
DesignNodeMap is the closed set of design node-map address keys (SPEC
design_node_map). design may address only these node paths.
var ForbiddenMetaKeys = []string{
"display_switch",
"display_target",
"if",
"when",
"show_if",
"_",
"seqtokey",
"__13hex__",
"$after",
"$before",
"$merge",
"$remove",
"xclass",
"xstyle",
}
ForbiddenMetaKeys are keys that must never appear in a model document,
at ANY depth. They have no field on any model struct; every Unmarshal
that owns an open bucket (and every typed slot / structure key) rejects
them via rejectForbiddenKeys. A schema layer expresses the same with
propertyNames:{not:{enum:[…]}}.
Condition-only meta keys (display_switch / display_target / if / when /
show_if) are gone — conditions live in condition maps and design.show.
Convention sigils (_) are gone; $after / $before / $merge / $remove are
expressed with $patch; xclass / xstyle fold into the design node map. The
x{key} comment pattern is handled by ForbiddenKeyPrefix, not this literal
list.
var RuleNames = []string{
"required", "email", "minlength", "maxlength", "min", "max", "match", "pattern",
"unique", "in", "range", "rangelength", "number", "digits", "equalTo", "containedIn", "typeFrom", "minFrom", "maxFrom", "notEqual",
"date", "dateISO", "enddate", "url", "accept", "mincount", "maxcount", "step",
"conditionTree",
"reference",
}
RuleNames are the registered rule names, the only keys of validate and
messages. The validate package's rule registry has exactly these rules.
FUNCTIONS
func ScanForbiddenKeys(spec any, rootPath []string) *compose.ComposeLoadError
ScanForbiddenKeys recursively scans a composed single spec (compose value
model: *compose.OMap / []any / scalars) for any forbidden meta key at any
depth. It returns a *compose.ComposeLoadError (code FORBIDDEN_META_KEY)
on the first hit, with the dotted path to the offending key in Message and
Trace; nil when the spec is clean.
The scan descends into every object value AND every array element (a
forbidden key nested inside an array of sub-specs is caught too). Map keys
are checked before descending into their values, so the reported path points
at the shallowest offending key. rootPath is the optional path prefix for
the error trace.
TYPES
type BehaviorSlot struct {
// Cancel is the false shape: cancels a composed-in behavior slot.
Cancel bool `json:"-"`
// Bare is the true shape: bare opt-in with no body.
Bare bool `json:"-"`
// Onchange is the opaque change-handler script.
Onchange any `json:"onchange,omitempty"`
// Onclick is the opaque click-handler script.
Onclick any `json:"onclick,omitempty"`
// Onload is the opaque load-handler script.
Onload any `json:"onload,omitempty"`
}
BehaviorSlot is the behavior role slot — common across all types.
Scripts are passed through opaquely to client JS and never enter the
expression engine (G1 / §4). false (Cancel) cancels a composed-in behavior.
A behavior's label is behavior.{action}.label, adjacent to the action.
Polymorphic: Cancel = the false shape, Bare = the true shape, otherwise the
body carries the {} shape. behavior is a closed bucket: Unmarshal rejects
any sub-key outside onchange / onclick / onload.
func (s *BehaviorSlot) MarshalJSON() ([]byte, error)
MarshalJSON emits a BehaviorSlot as false, true, or the body object.
func (s *BehaviorSlot) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a BehaviorSlot in its polymorphic shape.
type CompositionDirective struct {
// Ref is base inheritance (file / path). It expands before anything else.
Ref any `json:"$ref,omitempty"`
// Patch is the change set (add / remove / replace), applied after $ref.
Patch any `json:"$patch,omitempty"`
}
CompositionDirective is a composition key. $ref expands first (base
inheritance from a file / path; an unresolved $ref cannot load),
then $patch applies changes (add / remove / replace, JSON-Patch style,
with deep-path set support). Resolution order: $ref → $patch → single spec
→ field layer. $after / $before / $merge / $remove are forbidden meta keys;
$patch expresses those changes.
type ConditionMap struct {
// Keys holds condition expressions in declaration order; the last is
// typically ConditionDefaultKey.
Keys []string
// Values maps each condition expression to its result value.
Values map[string]any
}
ConditionMap is the declaration-ordered condition map (SPEC condition_map).
Keys are evaluated in declaration order; the first truthy key's value
is returned. If none match, the value under the literal "true" key (if
present) is returned, else null. The default key is always the literal
true (ConditionDefaultKey) — R4 forbids convention sigils such as "_".
Each key is an expressions.md §2 expression; the map is a thin wrapper that
re-invokes the engine, not a separate parser. A single ternary "...?...:..."
is the shorthand (same semantics). Evaluated value type — boolean | string |
number | null — is decided by the call site, not the map. Order is preserved
on a round trip.
func (c *ConditionMap) MarshalJSON() ([]byte, error)
MarshalJSON emits ConditionMap as a JSON object in declaration order.
func (c *ConditionMap) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes a condition map preserving declaration order.
type Content struct {
// Text is the monolingual value.
Text *string
// LangKeys holds language codes in declaration order.
LangKeys []string
// Lang is the per-language value, language code → text; a nil entry is a
// null (empty) label slot for that language (G3 — no effect).
Lang map[string]*string
// Null is the explicit null-content form: the whole content is empty/absent.
// It round-trips as the JSON literal null (SPEC §2 G3).
Null bool
}
Content is a content value that is a plain string, a language map (G3
content translation, e.g. {ko, en}), or null (empty content — identical to
absent, no render/validate effect, SPEC §2 G3). The language map preserves
declaration order on a round trip (LangKeys), since emission order is
observable; a per-language slot may itself be null (empty label for that
language), kept as a nil *string so the empty slot round-trips as null.
func (c *Content) MarshalJSON() ([]byte, error)
MarshalJSON emits a Content as null, a string, or an ordered language object
(per-language null slots emit as JSON null).
func (c *Content) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a Content from a JSON string, language object, or null,
preserving language-key order via a token walk. A per-language null value
is kept as a nil *string (G3 empty slot — no effect); a bare null is the
null-content form.
type DeclaredAttributes struct {
// Keys holds the attribute names in declaration order.
Keys []string
// Values maps each attribute name to its value.
Values map[string]string
}
DeclaredAttributes are the attributes a form field declares for an element,
in declaration order. Every value is a string; compilation checks the names.
func (a *DeclaredAttributes) MarshalJSON() ([]byte, error)
MarshalJSON emits DeclaredAttributes as a JSON object in declaration order.
func (a *DeclaredAttributes) UnmarshalJSON(data []byte) error
UnmarshalJSON reads DeclaredAttributes and rejects a value that is not an
object and a member value that is not a string.
type DesignNode struct {
// Class is the node's appearance class.
Class any `json:"class,omitempty"`
// Style is the node's inline appearance.
Style any `json:"style,omitempty"`
}
DesignNode is the appearance of one named DOM node within the design node
map: its class and inline style. Either may be an expression or condition
map. It is a closed bucket: Unmarshal rejects any key beyond class / style.
func (n *DesignNode) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a DesignNode and rejects any key beyond class / style.
type DesignSlot struct {
// Cancel is the false shape: cancels a composed-in design slot.
Cancel bool `json:"-"`
// Bare is the true shape: bare opt-in with no body.
Bare bool `json:"-"`
// Show is the display condition (expression or condition map), not a node.
// A false result hides the field; validation of a hidden field is skipped.
Show any `json:"show,omitempty"`
// Class is the appearance class for the field's own (main / input) node.
Class any `json:"class,omitempty"`
// Style is the inline appearance for the field's own node.
Style any `json:"style,omitempty"`
// Attributes are the attributes a form field declares for its control.
Attributes *DeclaredAttributes `json:"attributes,omitempty"`
// Layout is the layout a group declares for its fields: stacked, inline or line.
Layout string `json:"layout,omitempty"`
// Label is the appearance map for the label node.
Label *DesignNode `json:"label,omitempty"`
// Wrapper is the appearance map and the declared attributes of the wrapper node.
Wrapper *WrapperNode `json:"wrapper,omitempty"`
// Group is the appearance map for the group node.
Group *DesignNode `json:"group,omitempty"`
// Prepend is the appearance map for the prepend node.
Prepend *DesignNode `json:"prepend,omitempty"`
}
DesignSlot is the design role slot — appearance = display condition (Show) +
a per-DOM-node appearance map. R8: the targeted node is visible in the key;
per-node classes are declared here, never as element_class / label_class /
group_class / input_class / wrapper_class / prepend_class siblings.
Polymorphic: Cancel = the false shape, Bare = the true shape, otherwise the
body carries the {} shape. design is a closed bucket: Unmarshal rejects any
key outside show / class / style / attributes / layout / label / wrapper /
group / prepend, and any node key outside class / style, and attributes on
the wrapper (DesignNodeMap).
func (s *DesignSlot) MarshalJSON() ([]byte, error)
MarshalJSON emits a DesignSlot as false, true, or the body object.
func (s *DesignSlot) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a DesignSlot in its polymorphic shape.
type FieldSpec struct {
// Type is structure / identity — the field's widget type. Its target is a
// scalar, so type-dependent detail is isolated in the Options slot.
Type string `json:"type,omitempty"`
// Name is structure / identity — the field name (identifier). Explicit and
// semantic (R4).
Name string `json:"name,omitempty"`
// Default is structure / identity — the default value the evaluator reads
// when a path is unresolved (expressions.md §5 Path).
Default any `json:"default,omitempty"`
// Properties is structure / identity — the child-field map (group / object),
// in declaration order. It is the $ref / $patch composition entry point.
Properties *PropertyMap `json:"properties,omitempty"`
// Items is structure / identity — the choice source: a static array, a
// static value→label map, or a dynamic source. The dynamic
// source (type: search) is a model (name string OR nested
// {table,relations,keys} query) + a sibling api_server (runtime HTTP fn
// reference) + a placeholder static items, all isolated under it and
// preserved verbatim. Runtime resolution is out of scope (SPEC §6 R1).
Items *Items `json:"items,omitempty"`
// Multiple is structure / identity — repeated rows (true = on). Polymorphic
// false | {} | true; repetition dependents are isolated under it. Row
// identity (G4) is NOT a multiple field — at runtime it is a hidden server PK
// carried in the submitted data, not a build-time spec field (no id key).
Multiple *Multiple `json:"multiple,omitempty"`
// Lang is structure / identity — the input-multilingual dimension (the field
// value is per language, G3). Same structural axis as Multiple. Polymorphic
// false | {} | true; lang dependents are isolated under it.
Lang *Lang `json:"lang,omitempty"`
// Label is content — the field label. May be a language map {ko,en} (G3
// axis 1). The label sits at top level, next to the field it modifies.
Label *Content `json:"label,omitempty"`
// Description is content — the description (may be a language map, G3).
Description *Content `json:"description,omitempty"`
// Placeholder is content — the placeholder text.
Placeholder *Content `json:"placeholder,omitempty"`
// Prepend is content — text before the input (distinct from the
// design.prepend node appearance: this is the content text itself).
Prepend *Content `json:"prepend,omitempty"`
// Append is content — text after the input.
Append *Content `json:"append,omitempty"`
// Help is content — help text (may be a language map, G3).
Help *Content `json:"help,omitempty"`
// Content is content — the control text of a button or action field (may
// be a language map, G3).
Content *Content `json:"content,omitempty"`
// Validate is the validation role slot (common, every field).
Validate *ValidateSlot `json:"validate,omitempty"`
// Messages are error message overrides keyed by registered rule name.
Messages *Messages `json:"messages,omitempty"`
// Design is the appearance role slot — display condition plus a per-node
// appearance map.
Design *DesignSlot `json:"design,omitempty"`
// Behavior is the behavior role slot — opaque scripts that never pass
// through the expression engine.
Behavior *BehaviorSlot `json:"behavior,omitempty"`
// Options is the type-dependent role slot — the type defines and validates
// it; the core does not participate.
Options *OptionsSlot `json:"options,omitempty"`
// Buttons are the form buttons rendered in the form footer. Honored on the
// form root only.
Buttons []FormButton `json:"buttons,omitempty"`
// Action is the submission target kept unchanged in the template. Honored on the
// form root only.
Action *FormAction `json:"action,omitempty"`
// DataLimits bounds submitted data on the form root (CUSC-087).
DataLimits *FormDataLimits `json:"dataLimits,omitempty"`
}
FieldSpec is the canonical model field. Every member maps to one top-level
SPEC key. The four role slots (Validate / Design / Behavior / Options) and
the polymorphic structure keys (Multiple / Lang) are pointers so the absent
/ false / {} / true shapes all survive a round trip.
A schema layer over this type rejects every key not listed below and every
ForbiddenMetaKey; the absent condition-only meta keys must never return.
func (f *FieldSpec) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes a FieldSpec and rejects any forbidden meta key present
at the top level (the schema's additionalProperties:false / propertyNames
guard, mechanized).
type FormAction struct {
// Method is the HTTP method.
Method string `json:"method,omitempty"`
// URL is the submission URL.
URL string `json:"url,omitempty"`
// Enctype is the submission encoding.
Enctype string `json:"enctype,omitempty"`
}
FormAction is the submission target of the form, kept unchanged in the
template.
type FormButton struct {
// Type is submit, reset, button or link.
Type string `json:"type"`
// Text is the button text, optionally a language map.
Text *Content `json:"text,omitempty"`
// Name is the submitted name.
Name string `json:"name,omitempty"`
// Value is the submitted value.
Value string `json:"value,omitempty"`
// Href is the link target.
Href string `json:"href,omitempty"`
// Design is the button appearance.
Design *DesignSlot `json:"design,omitempty"`
// Behavior holds opaque behavior scripts.
Behavior *BehaviorSlot `json:"behavior,omitempty"`
}
FormButton is one form button. A button or link needs text; a link needs
href.
type FormDataLimits struct {
// Depth is the maximum number of nested container levels.
Depth *int `json:"depth,omitempty"`
// Values is the maximum number of submitted value nodes.
Values *int `json:"values,omitempty"`
}
FormDataLimits declares the submission depth and value limits (CUSC-087).
A missing member uses the validation default (CUVR-083).
type Items struct {
// Static is the static choice array, when items is a literal list.
Static []any `json:"-"`
// StaticMapKeys holds the value→label map keys in declaration order (the
// option values), so the map round-trips in order.
StaticMapKeys []string `json:"-"`
// StaticMap is the value→label map: option value (key) → label (string |
// LangMap | null), kept as raw JSON so any label shape round-trips.
StaticMap map[string]json.RawMessage `json:"-"`
// Model is the dynamic source model: a name string OR a nested
// {table, relations, keys} query. Raw JSON so both shapes round-trip; the
// runtime, not this model, builds and runs the query.
Model json.RawMessage `json:"model,omitempty"`
// Method is the dynamic source method.
Method any `json:"method,omitempty"`
// Table is the dynamic source table (top-level shorthand of model.table).
Table any `json:"table,omitempty"`
// Relations is the dynamic source relations.
Relations any `json:"relations,omitempty"`
// ApiServer is the runtime HTTP-endpoint fn reference (an opaque source
// callback string). Preserved verbatim; the engine never calls it —
// invoking the endpoint is runtime, out of scope.
ApiServer json.RawMessage `json:"api_server,omitempty"`
// PlaceholderItems is the placeholder static items that coexists with the
// dynamic source (the pre-fetch choices shown before the source resolves,
// often [] or a single {"": "선택하세요"} prompt). Raw JSON so the array /
// value→label shapes round-trip; the runtime replaces it with fetched rows.
PlaceholderItems json.RawMessage `json:"items,omitempty"`
}
Items is the choice source: a static array (Static), a static value→label
map (StaticMap, G3), or a dynamic source. It is the dependency-isolation
bucket for the items target — dynamic-source keys live here, not at top
level.
A static value→label map (SPEC §2 G3) has the option VALUE as the KEY and
the display label (string | LangMap | null) as the value. The label is
display-only and never a membership value; an empty (null) label has no
effect.
A dynamic source is STRUCTURE ONLY. The supported shape (type: search) is a
Model (a name string OR a nested {table, relations, keys} relational query)
plus a sibling ApiServer (a runtime HTTP-endpoint fn reference) plus a
placeholder static Items. Model / ApiServer / Items are kept as raw JSON and
preserved verbatim — the engine never runs the query or calls the endpoint.
Runtime resolution is out of scope (SPEC §6 R1).
func (i *Items) MarshalJSON() ([]byte, error)
MarshalJSON emits Items as the static array, the static value→label map (in
declaration order), else the dynamic source object.
func (i *Items) UnmarshalJSON(data []byte) error
UnmarshalJSON reads Items as a static array, a static value→label map (G3),
or a dynamic source object. An items object whose keys are not all
dynamic-source keys is a static value→label map (key = option value, value =
display label).
type Lang struct {
// Cancel is the false shape: lang disabled (cancels a composed-in lang).
Cancel bool `json:"-"`
// Enabled is the true shape: the bare on switch with no body.
Enabled bool `json:"-"`
// Mode is the multilingual mode.
Mode any `json:"mode,omitempty"`
// Only has two shapes (SPEC §3 C): a language allowlist []string
// (["ko","en"]) OR a per-language override map
// map[string]{validate/design/behavior/options} ({ja:{validate:…}}). The
// allowlist restricts rendered languages; the override map redefines role
// slots per language. Held as any so both shapes round-trip.
Only any `json:"only,omitempty"`
// Name is the per-language name binding.
Name any `json:"name,omitempty"`
// Key is the per-language key binding.
Key any `json:"key,omitempty"`
// Frame toggles the language frame.
Frame any `json:"frame,omitempty"`
// Title is the language-frame title.
Title any `json:"title,omitempty"`
// GroupClass is the language group's appearance class.
GroupClass any `json:"group_class,omitempty"`
}
Lang is the input-multilingual dimension (the field value is per language,
G3 axis 2) and the dependency-isolation bucket for the lang target —
multilingual control keys live under it, never at top level. Same structural
axis as Multiple. Polymorphic false | {} | true.
lang:append / langs / lang_name / lang_key / remove_lang_frame /
remove_lang_title / lang_group_class are NOT fields here; the canonical
keys are mode / only / name / key / frame / title / group_class (R2 / R4).
The magic `:` token in lang:append is rejected outright. lang is a closed
bucket: Unmarshal rejects any key not modeled below.
func (l *Lang) MarshalJSON() ([]byte, error)
MarshalJSON emits Lang as false, true, or the body object.
func (l *Lang) UnmarshalJSON(data []byte) error
UnmarshalJSON reads Lang in its polymorphic false | {} | true shape.
type Messages struct {
// Keys holds rule names in declaration order.
Keys []string
// Values maps each rule name to its message.
Values map[string]string
}
Messages are error message overrides keyed by registered rule name,
in declaration order. Every message is a string.
func (m *Messages) MarshalJSON() ([]byte, error)
MarshalJSON emits Messages as a JSON object in declaration order.
func (m *Messages) UnmarshalJSON(data []byte) error
UnmarshalJSON reads Messages and rejects a key that is not a registered rule
name and a message that is not a string.
type Multiple struct {
// Cancel is the false shape: multiple disabled (cancels a composed-in
// multiple).
Cancel bool `json:"-"`
// Enabled is the true shape: the bare on switch with no body.
Enabled bool `json:"-"`
// DataOnly is the "only" shape: rows come only from the data.
DataOnly bool `json:"-"`
// Only declares rows that exist only in the data.
Only any `json:"only,omitempty"`
// Min is the minimum row count.
Min any `json:"min,omitempty"`
// Max caps the row count.
Max any `json:"max,omitempty"`
// Copy toggles the row copy control.
Copy any `json:"copy,omitempty"`
// Sortable toggles row reordering.
Sortable any `json:"sortable,omitempty"`
// Title names the direct child field whose value titles each row.
Title any `json:"title,omitempty"`
// Controls positions row controls: header, footer or outline.
Controls any `json:"controls,omitempty"`
// Header selects static or sticky row headers.
Header any `json:"header,omitempty"`
// Onclick is the row-action script.
Onclick any `json:"onclick,omitempty"`
}
Multiple is repeated rows (true = on) and the dependency-isolation bucket
for the multiple target — repetition-control keys live under it, never at
top level. Polymorphic false | {} | true | "only"; "only" is the same as
{"only": true}, rows that exist only in the data (docs/spec/schema.md).
Row identity is not a Multiple field. Repeated data is an object keyed by
row identity, and object member order is row order; the specification has no
hidden identity or order field (see docs/spec/form-runtime.md).
multiple_max / sortable* / add_buttons / remove_list_button /
list_button_text / multiple_button_onclick are NOT fields here; the
canonical keys are max / sortable / copy / onclick (R2 / R4). multiple is a
closed bucket: Unmarshal rejects any key not modeled below.
func (m *Multiple) MarshalJSON() ([]byte, error)
MarshalJSON emits Multiple as false, true, or the body object.
func (m *Multiple) UnmarshalJSON(data []byte) error
UnmarshalJSON reads Multiple in its polymorphic false | {} | true | "only"
shape.
type OptionsSlot struct {
// Cancel is the false shape: cancels a composed-in options slot.
Cancel bool `json:"-"`
// Bare is the true shape: bare opt-in with no body.
Bare bool `json:"-"`
// KeywordMinLength is the minimum keyword length before lookup fires.
KeywordMinLength any `json:"keyword_min_length,omitempty"`
// MarkerDraggable toggles a draggable map marker.
MarkerDraggable any `json:"marker_draggable,omitempty"`
// Zoom is the initial map zoom level.
Zoom any `json:"zoom,omitempty"`
// GeometryType is the map geometry kind.
GeometryType any `json:"geometry_type,omitempty"`
// MaxTags caps the number of tags.
MaxTags any `json:"max_tags,omitempty"`
// CheckboxLabel is the inline checkbox label.
CheckboxLabel any `json:"checkbox_label,omitempty"`
// OnLabel is the toggle's on-state label.
OnLabel any `json:"on_label,omitempty"`
// Collapse toggles a collapsible container.
Collapse any `json:"collapse,omitempty"`
// Expend toggles an expandable container.
Expend any `json:"expend,omitempty"`
// ViewTotal toggles a total view.
ViewTotal any `json:"view_total,omitempty"`
// Stepper toggles stepper controls.
Stepper any `json:"stepper,omitempty"`
// BlankMessage is the empty-state message.
BlankMessage any `json:"blank_message,omitempty"`
// Callback is the type-dependent callback reference.
Callback any `json:"callback,omitempty"`
// Event is the type-dependent event reference.
Event any `json:"event,omitempty"`
// Extra holds any further type-owned option keys not modeled above. The
// core does not interpret them; only the type does. Forbidden meta keys are
// rejected here too.
Extra map[string]any `json:"-"`
}
OptionsSlot is the type-dependent role slot and the dependency-isolation
bucket for the scalar Type target: the type defines and validates these
settings, the core stays uninvolved, and a new widget needs no core change.
Container-type chrome and type-dependent scripts / callbacks live here too.
Polymorphic: Cancel = the false shape, Bare = the true shape, otherwise the
body carries the {} shape. The named fields below are the documented scalar
dependents; Extra holds any further type-owned keys the core does not model.
Extra is an open bucket but still rejects every ForbiddenMetaKey on
Unmarshal — extension is allowed, forbidden keys are blocked one level under
the bucket.
func (s *OptionsSlot) MarshalJSON() ([]byte, error)
MarshalJSON emits an OptionsSlot as false, true, or the body object,
inlining Extra keys after the named fields.
func (s *OptionsSlot) UnmarshalJSON(data []byte) error
UnmarshalJSON reads an OptionsSlot in its polymorphic shape, routing unknown
keys into Extra and rejecting every ForbiddenMetaKey first.
type PropertyMap struct {
// Keys holds property names in declaration order.
Keys []string
// Values maps each property name to its FieldSpec.
Values map[string]*FieldSpec
}
PropertyMap is a declaration-ordered child-field map (group / object). Order
is preserved on a round trip because emission and composition depend on it.
It is the $ref / $patch composition entry point.
func (p *PropertyMap) MarshalJSON() ([]byte, error)
MarshalJSON emits PropertyMap as a JSON object in declaration order.
func (p *PropertyMap) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes a property object while preserving declaration order
via a token walk. Each child value is decoded into a FieldSpec, so forbidden
keys nested in a child are rejected by the child's own UnmarshalJSON.
type ValidateSlot struct {
// Cancel is the false shape: cancels a composed-in validate slot.
Cancel bool `json:"-"`
// Bare is the true shape: bare opt-in with no body.
Bare bool `json:"-"`
// Required is the required rule (bool, expression, or condition map).
Required any `json:"required,omitempty"`
// Email is the email-format rule.
Email any `json:"email,omitempty"`
// Match is the cross-field match rule.
Match any `json:"match,omitempty"`
// Extra holds the further registered rules not modeled above.
Extra map[string]any `json:"-"`
}
ValidateSlot is the validate role slot. Polymorphic: Cancel = the false
shape (cancels composed inheritance), Bare = the true shape, otherwise the
body fields carry the {} shape.
Each sub-key value may be an expression or a condition map, so conditional
validation is expressed without a separate key (e.g. required: '.subscribe',
email: true). validate accepts the registered rule names only (RuleNames):
Extra keeps every further registered rule, and any other key is rejected on
Unmarshal.
func (s *ValidateSlot) MarshalJSON() ([]byte, error)
MarshalJSON emits a ValidateSlot as false, true, or the body object.
func (s *ValidateSlot) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a ValidateSlot in its polymorphic shape.
type WrapperNode struct {
// Class is the node's appearance class.
Class any `json:"class,omitempty"`
// Style is the node's inline appearance.
Style any `json:"style,omitempty"`
// Attributes are the attributes declared for the node root.
Attributes *DeclaredAttributes `json:"attributes,omitempty"`
}
WrapperNode is the appearance of the wrapper node (the node root) and the
attributes a form field declares for it. It is a closed bucket: Unmarshal
rejects any key beyond class / style / attributes.
func (n *WrapperNode) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a WrapperNode and rejects any key beyond class / style /
attributes.
package compose // import "github.com/polyspec/crudui/packages/validator/go/validator/compose"
Package compose is the Go port of the model composition engine (SPEC §5, G5).
The parser's FIRST pass: expand $ref (base inheritance) then $patch
(add/remove/replace + deep-path set) into a single, composition-free spec,
BEFORE the field layer / validation / render. Unresolved composition is a LOAD
ERROR (ComposeLoadError) — never valid:true.
Byte-for-byte parity with the JS reference (validator-ts/src/compose). Both load
the SAME shared fixture tests/fixtures/compose/cases.json and must reproduce
it identically (G-B 4-language idempotence). interface{} is spelled any (Go ≥
1.18). No eval.
FUNCTIONS
func DecodeOrdered(data []byte) (any, error)
DecodeOrdered parses a JSON document into the engine value universe (*OMap
/ []any / scalars), preserving object declaration order. It is the exported
entry the model validate pass uses to load a spec into the compose value
model before composing it. Same semantics as the internal decodeOrdered.
Strings and member names keep their text exactly: an unpaired surrogate
escape becomes the three-byte encoding of that surrogate and a byte that is
not UTF-8 is kept, so both remain text that is not valid UTF-8 and the input
text check rejects them (docs/spec/input-text.md). Nothing is replaced with
U+FFFD.
func OrderMembers(value any) any
OrderMembers returns a copy of a specification value in specification member
order, the own-property order of a JavaScript object: member names that are
array indexes come first in ascending numeric order, then all other names in
insertion order. Objects are reordered at every depth; arrays stay arrays
with their elements ordered the same way; scalars are returned unchanged.
Record data keeps insertion order and is never passed here.
TYPES
type ComposeErrorCode string
ComposeErrorCode is a stable machine-readable load-error code (one per
unresolved-behavior class). Mirrors errors.ts ComposeErrorCode exactly.
const (
// RefFileNotFound: $ref points at a file that does not exist (the file cannot be loaded).
RefFileNotFound ComposeErrorCode = "REF_FILE_NOT_FOUND"
// RefFormatError: $ref string is malformed: "(…" with no closing ").keys", or empty path.
RefFormatError ComposeErrorCode = "REF_FORMAT_ERROR"
// RefDetectKeyNotFound: a detectKey path segment (or the trailing "properties") is absent.
RefDetectKeyNotFound ComposeErrorCode = "REF_DETECT_KEY_NOT_FOUND"
// RefCycle: $ref cycle detected (A→B→A); resolution stops instead of recursing forever.
RefCycle ComposeErrorCode = "REF_CYCLE"
// RefValueType: $ref value is neither a string nor an array of strings.
RefValueType ComposeErrorCode = "REF_VALUE_TYPE"
// PatchShape: $patch is not an object of deep-path → value entries.
PatchShape ComposeErrorCode = "PATCH_SHAPE"
// PatchPathConflict: $patch op targets a path whose intermediate node is a non-object scalar.
PatchPathConflict ComposeErrorCode = "PATCH_PATH_CONFLICT"
// PatchRemoveTargetMissing: $patch remove targets a path that does not exist (strict remove).
PatchRemoveTargetMissing ComposeErrorCode = "PATCH_REMOVE_TARGET_MISSING"
// ForbiddenMetaKey: a forbidden meta key (condition-only / composition-directive / magic-symbol,
// or an x{key} comment that survived x-strip) was found at any depth in the
// composed single spec. The forbidden-scan runs in the LOAD path after compose
// expansion; a hit is a load failure, never valid:true. Mirrors JS forbidden-scan.ts.
ForbiddenMetaKey ComposeErrorCode = "FORBIDDEN_META_KEY"
// InvalidRuleParameter: a validate rule parameter outside the rule's
// definition (docs/spec/validation-rules.md, Parameter errors). Trace is the
// field's declaration path.
InvalidRuleParameter ComposeErrorCode = "INVALID_RULE_PARAMETER"
// InvalidRulePattern: a pattern or match parameter outside the CRUDUI pattern
// language. Trace is the field's declaration path.
InvalidRulePattern ComposeErrorCode = "INVALID_RULE_PATTERN"
// UnknownRule: a validate or messages key that is not a registered rule name
// (docs/spec/validation-rules.md, Parameter errors). Trace is the field's
// declaration path.
UnknownRule ComposeErrorCode = "UNKNOWN_RULE"
// InvalidText: a string or member name in the specification or a composition
// file that is not valid UTF-8 (docs/spec/input-text.md). Trace is its path.
InvalidText ComposeErrorCode = "INVALID_TEXT"
)
type ComposeLoadError struct {
// Code is the stable machine-readable code (one per unresolved-behavior class).
Code ComposeErrorCode
// Message is the human-readable detail.
Message string
// Trace is the composition path stack when the error occurred (for cycle/trace).
Trace []string
}
ComposeLoadError is every composition load failure. NOT a validation error.
Composition is a pre-processing pass that runs BEFORE validation/render:
the parser expands $ref/$patch into a single spec first (G5). An unresolved
composition is therefore NOT a validation failure (valid:false) — it is a
LOAD FAILURE: the spec itself does not come into existence. Never let an
unresolved $ref pass as valid:true.
func (e *ComposeLoadError) Error() string
Error implements the error interface.
type ComposeOptions struct {
// Basepath for relative $ref resolution.
Basepath string
}
ComposeOptions configures a composition pass.
type FileLoader interface {
// Normalize resolves path against basepath to the canonical key the map uses.
Normalize(path, basepath string) string
// Load returns the parsed document at the canonical key, or a NOT_FOUND error.
Load(key string) (*OMap, error)
}
FileLoader resolves a normalized file key to its parsed document. Load
returns a *ComposeLoadError with code RefFileNotFound when the key is absent
— the engine relies on that exact code (an unresolved $ref is a load error,
never valid:true).
type MemoryLoader struct {
// Has unexported fields.
}
MemoryLoader is an in-memory loader over a fixed { key: doc } map. Shared
fixtures pass a file set here; the engine resolves $ref against it with no
disk access. Keys are the normalized identifiers (Normalize output).
func NewMemoryLoader(files map[string]*OMap) *MemoryLoader
NewMemoryLoader builds a MemoryLoader from a { key: doc } map. Each doc
must be an *OMap (a parsed object). A defensive clone is taken on Load so
resolution never mutates the source set.
func (l *MemoryLoader) Load(key string) (*OMap, error)
Load returns a copy of the parsed doc at key in specification member order,
or a RefFileNotFound load error. The copy keeps resolution from mutating the
source file set (mirrors structuredClone(doc) on the JS side).
func (l *MemoryLoader) Normalize(path, basepath string) string
Normalize passes an absolute path through unchanged; a relative path is
prefixed by basepath.
type OMap struct {
// Has unexported fields.
}
OMap is an insertion-order-preserving string map — the Go stand-in for a JS
object. Declaration order is LOAD-BEARING in composition (a key declared
after $ref overrides the inherited base; a key before it is overridden by
it). Go's native map randomizes iteration, and encoding/json drops object
key order, so the engine carries order explicitly in OMap and decodes JSON
through it.
The compose engine operates only on OMap / []any / scalars — the same value
universe the JS engine sees (object / array / primitive). Every node that is
a JS "plain object" is an *OMap here.
func ComposeProperties(properties *OMap, loader FileLoader, opts ComposeOptions) (*OMap, error)
ComposeProperties composes a properties map: expand $ref to a base,
overlay $patch, return the single (composition-free) properties map. Named
sibling keys follow declaration order — a key declared after $ref overrides
the base; a key declared before it is overridden by the base.
The input and the result are in specification member order (see
OrderMembers).
func ComposeSpec(spec *OMap, loader FileLoader, opts ComposeOptions) (*OMap, error)
ComposeSpec composes a full field spec: expand a field-level $ref/$patch,
then recurse into its properties (which may itself compose). Returns the
single spec. The input and the result are in specification member order (see
OrderMembers).
func NewOMap() *OMap
NewOMap returns an empty ordered map.
func (m *OMap) Clone() *OMap
Clone makes a shallow copy (new key slice + value map; values shared). The
engine never mutates shared value subtrees in place — it rebuilds (mirrors
the JS spread `{ ...node }`).
func (m *OMap) Delete(key string)
Delete removes key (and its position). A no-op if absent.
func (m *OMap) Get(key string) (any, bool)
Get returns the value for key and whether it was present.
func (m *OMap) Has(key string) bool
Has reports whether key is present.
func (m *OMap) Keys() []string
Keys returns the keys in insertion order (a copy; mutating it is harmless).
func (m *OMap) Len() int
Len returns the number of entries.
func (m *OMap) MarshalJSON() ([]byte, error)
MarshalJSON emits the entries in insertion order so a round trip is stable.
func (m *OMap) Set(key string, value any)
Set inserts or updates key. A new key appends to the order; an existing key
keeps its position (matching JS object assignment semantics).
type RawMember struct {
Name string
Value []byte
}
RawMember is one object member of a JSON document: its name, kept exactly as
DecodeOrdered keeps text, and the raw JSON text of its value.
func DecodeRawMembers(data []byte) ([]RawMember, error)
DecodeRawMembers splits a JSON object document into its members in document
order without decoding the values. A document that is not one JSON object is
an error.
package expr // import "github.com/polyspec/crudui/packages/validator/go/validator/expr"
Package expr is the Go model expression engine (expressions.md §0-§7).
Pipeline: string -Lexer-> []Token -Parser-> Node (AST) -[Evaluator + data]->
value. The three stages are separate (GRAMMAR §0): the lexer knows no
grammar, the parser knows no data, the evaluator knows no string. No eval,
no regex/string split evaluation (GRAMMAR §10) — a single forward scan plus
recursive descent.
The single truth is the shared 4-language fixture tests/fixtures/expr/cases.json
(already passing in JS and PHP). Tokens, AST shape, and evaluated value/truthy
are byte-compatible with the JS reference (validator-ts ConditionParser.ts
+ PathResolver.ts). AST serialization omits position (Rust holds none → the
4-language equivalence compares position-free nodes).
CONSTANTS
const DefaultKey = "true"
DefaultKey is the literal expression used as the condition-map default.
const MaxExpressionDepth = 64
MaxExpressionDepth is the most nodes on a path from the root of an
expression's syntax tree to a leaf. A deeper expression is a parse error in
every runtime.
FUNCTIONS
func ClearCache()
ClearCache clears the parsed-AST cache.
func Evaluate(expression string, formData map[string]any, currentPath []string, rowKeys []int) (bool, error)
Evaluate evaluates an expression to a truthy boolean (show/display call
site).
func EvaluateValue(expression string, formData map[string]any, currentPath []string, rowKeys []int) (any, error)
EvaluateValue evaluates an expression to its raw value
(class/style/number/null call site).
func IsConditionExpression(value any) bool
IsConditionExpression distinguishes expression strings from literal
appearance values.
func IsNumber(value any) bool
IsNumber reports whether a value is carried by a Go numeric type.
func IsTruthy(value any) bool
IsTruthy is the truthy test (JS Boolean, GRAMMAR §6). null/false/0/0.0/""
are falsy; "0", "false", empty list, empty object, and every non-empty value
are truthy.
func NumberValue(value any) (float64, bool)
NumberValue returns the double value of a number, whatever Go numeric type
carries it: every signed and unsigned integer width, both float widths and a
json.Number. An integer converts to the nearest double. A json.Number whose
text does not read as a double is not a number.
func RelativeBase(currentPath []string, rowKeys []int, levelsUp int) []string
RelativeBase returns the path that a relative path with levelsUp further
dots starts from: the current field is one level and each further dot
moves one more level up, and a row key leaves together with the name of its
collection (expressions.md, "Evaluation").
func ResolveConditionMap(entries []ConditionEntry, formData map[string]any, currentPath []string, rowKeys []int) (any, error)
ResolveConditionMap resolves an ordered condition map against form data.
Declaration order is the slice order. The default key (literal "true") is
matched by text, not by evaluation, so it never short-circuits an earlier
real condition. Returns (value, nil) on a default fallthrough miss with no
default key present.
func ValueAtSegments(data any, path []string) any
ValueAtSegments reads the value at concrete path segments (JS
getValueByPath). A missing key, or a descent into a value that is neither an
object nor an array, yields nil. Numeric segments index into arrays.
TYPES
type BinaryNode struct {
Operator string
Left Node
Right Node
}
BinaryNode is a comparison (== != > >= < <=) or logical (&& ||) operation.
func (n *BinaryNode) ToMap() map[string]any
ToMap serializes the Binary node and its operands to the shared-fixture AST
shape {type, operator, left, right}.
type ConditionEntry struct {
Expr string
Value any
}
ConditionEntry is one ordered expr→value pair of a condition map.
type Evaluator struct {
// Has unexported fields.
}
Evaluator walks an AST against form data (GRAMMAR §5/§6/§7, JS PathResolver
parity). The shared 4-language fixture is generated from the JS reference
engine, so this evaluator mirrors validator-ts PathResolver function for
function:
- Evaluate(node) → bool (JS evaluateCondition): isTruthy of the resolved
value for Path/Literal; logical short-circuit; comparison; in; unary;
a ternary is isTruthy(its branch value).
- EvaluateValue(node) → value (JS evaluateExpressionValue): a ternary
returns its branch value; everything else returns the boolean condition.
truthy (GRAMMAR §6): null/false/0/"" falsy; "0", "false", [], {}, and every
non-empty value truthy (matches JS Boolean: empty array/object are truthy).
== / != use JS loose equality; > >= < <= coerce BOTH sides to number and
compare numerically — no lexicographic branch.
No eval / no regex evaluation (GRAMMAR §10) — pure AST walk.
func NewEvaluator(formData map[string]any, currentPath []string, rowKeys []int) *Evaluator
NewEvaluator binds form data, the current field path (the path of the field
carrying the condition, including its own name) and the positions of the row
keys in that path.
func (e *Evaluator) Evaluate(node Node) bool
Evaluate evaluates a node to a boolean condition result (JS
evaluateCondition).
func (e *Evaluator) EvaluateValue(node Node) any
EvaluateValue evaluates a node to a value (JS evaluateExpressionValue).
A ternary returns its chosen branch's raw value; every other node returns
its boolean condition result (the shared fixture's value for non-ternary
mirrors truthy).
type GroupNode struct {
Expression Node
}
GroupNode is a parenthesized expression.
func (n *GroupNode) ToMap() map[string]any
ToMap serializes the Group node and its inner expression to the
shared-fixture AST shape {type, expression}.
type InNode struct {
Negated bool
Value Node
List []Node
}
InNode is value in/not-in list. List holds Literal nodes.
func (n *InNode) ToMap() map[string]any
ToMap serializes the In node and its list to the shared-fixture AST shape
{type, negated, value, list}.
type Lexer struct {
// Has unexported fields.
}
Lexer turns a condition string into a []Token ending in EOF (GRAMMAR §1,
JS Lexer parity). WHITESPACE is consumed but never emitted. The token order,
multi-char operator precedence, multi-dot handling, string escapes,
and number rules mirror validator-ts exactly, because the shared fixture's
tokens array is byte-compared across 4 languages.
Single forward scan over bytes — no regex split, no string split (GRAMMAR
§10). Identifiers are ASCII ([A-Za-z_][A-Za-z0-9_]*), so byte scanning is
exact.
func NewLexer(input string) *Lexer
NewLexer builds a lexer over the given expression string.
func (l *Lexer) Tokenize() []Token
Tokenize scans the whole input. WHITESPACE tokens are dropped; an EOF token
is always appended last.
type LiteralNode struct {
ValueType string
Value any
}
LiteralNode is a constant. ValueType is "string"|"number"|"boolean"|"null".
func (n *LiteralNode) ToMap() map[string]any
ToMap serializes the Literal node to the shared-fixture AST shape {type,
valueType, value}.
type Node interface {
// ToMap serializes the node (and its children) to the shared-fixture AST shape.
ToMap() map[string]any
}
Node is one AST node. ToMap renders the JS serialization shape.
func Parse(expression string) (node Node, err error)
Parse parses an expression into its AST (cached). Returns *ParseError on
malformed input.
type ParseError struct {
Message string
}
ParseError is raised by the lexer or parser on malformed input. It is
panicked internally and recovered by Parse/Tokenize wrappers into a returned
error.
func (e *ParseError) Error() string
Error returns the parse failure message (implements the error interface).
type Parser struct {
// Has unexported fields.
}
Parser builds an AST from a token stream (GRAMMAR §3/§4, JS Parser parity).
Recursive descent (EBNF, lowest precedence first):
ternary = or [ "?" ternary ":" ternary ] (right-associative)
or = and { "||" and }
and = not { "&&" not }
not = "!" not | comparison
comparison = primary [ cmp_op cmp_value | in_op value_list ]
primary = "(" or ")" | path | literal
path = [ "." | ".." ] identifier { "." (identifier | number | "*") }
Precedence and right-associativity come from the productions, not a
heuristic. No eval, no split (GRAMMAR §10).
func NewParser(tokens []Token) *Parser
NewParser builds a parser over a lexer's token stream (must end in EOF).
func (p *Parser) Parse() Node
Parse parses the full token stream into one AST root. Panics with
*ParseError on trailing tokens or malformed input; callers use Parse() in
expression.go to recover into an error.
type PathNode struct {
Relative bool
LevelsUp int
Segments []PathSegment
}
PathNode is a field reference. Relative true => "."/".."; LevelsUp is
parents to ascend (".x"=0, "..x"=1). Segments are the dotted path after the
prefix.
func (n *PathNode) ToMap() map[string]any
ToMap serializes the Path node and its segments to the shared-fixture AST
shape {type, relative, levelsUp, segments}.
type PathSegment struct {
Type string
Value string // identifier name
Index int // index value
}
PathSegment is one Path step. Type is "identifier", "wildcard", or "index".
func (s PathSegment) ToMap() map[string]any
ToMap serializes the segment to the JS segment shape (index value as
number).
type TernaryNode struct {
Condition Node
TrueValue Node
FalseValue Node
}
TernaryNode is cond ? trueValue : falseValue (right-associative).
func (n *TernaryNode) ToMap() map[string]any
ToMap serializes the Ternary node and its children to the shared-fixture AST
shape {type, condition, trueValue, falseValue}.
type Token struct {
Type TokenType
Value string
Literal any
}
Token is a single lexical token. Value is the raw source text; Literal is
the parsed value for value tokens (string content, number int/float, bool,
the dot count for DOT_DOT) and nil otherwise.
No position field: the 4-language token contract excludes positions (the
Rust AST holds none), matching the shared fixture's token shape.
func Tokenize(expression string) (tokens []Token, err error)
Tokenize tokenizes an expression. WHITESPACE excluded; ends with EOF.
Returns an error only if the lexer hits malformed input (e.g. unterminated
string).
func (t Token) ToMap() map[string]any
ToMap serializes to the shared-fixture token shape {type, value, literal}.
type TokenType string
TokenType tags a lexical token (expressions.md §1, JS TokenType parity).
String-valued so a fixture's token.type string ("DOT"|"IDENTIFIER"|...) maps
directly. WHITESPACE never reaches the token slice (the lexer drops it);
EOF is always the final token. INVALID is one unrecognized character.
Intentionally absent (GRAMMAR §1·§10): arithmetic + - * / %, function calls,
method calls, regex, assignment =, bitwise, root path /. Do not add them.
const (
TokenString TokenType = "STRING"
TokenNumber TokenType = "NUMBER"
TokenBoolean TokenType = "BOOLEAN"
TokenNull TokenType = "NULL"
TokenIdentifier TokenType = "IDENTIFIER"
TokenDot TokenType = "DOT"
TokenDotDot TokenType = "DOT_DOT"
TokenAsterisk TokenType = "ASTERISK"
TokenEQ TokenType = "EQ"
TokenNE TokenType = "NE"
TokenGT TokenType = "GT"
TokenGE TokenType = "GE"
TokenLT TokenType = "LT"
TokenLE TokenType = "LE"
TokenAnd TokenType = "AND"
TokenOr TokenType = "OR"
TokenNot TokenType = "NOT"
TokenIn TokenType = "IN"
TokenNotIn TokenType = "NOT_IN"
TokenLParen TokenType = "LPAREN"
TokenRParen TokenType = "RPAREN"
TokenLBracket TokenType = "LBRACKET"
TokenRBracket TokenType = "RBRACKET"
TokenComma TokenType = "COMMA"
TokenQuestion TokenType = "QUESTION"
TokenColon TokenType = "COLON"
TokenEOF TokenType = "EOF"
TokenWhitespace TokenType = "WHITESPACE"
TokenInvalid TokenType = "INVALID"
)
The Token* constants enumerate every TokenType the lexer emits
(expressions.md §1). Their string values are the canonical fixture token
type names and are byte-compatible with the JS reference. TokenWhitespace
and TokenInvalid are lexer-internal: WHITESPACE is dropped before the token
slice and INVALID marks one unrecognized character.
type UnaryNode struct {
Operator string
Operand Node
}
UnaryNode is the logical-not operation ("!" operand).
func (n *UnaryNode) ToMap() map[string]any
ToMap serializes the Unary node and its operand to the shared-fixture AST
shape {type, operator, operand}.
package conformance // import "github.com/polyspec/crudui/packages/validator/go/validator/internal/conformance"
Package conformance records the validation fixtures through the shared Go test
support.
FUNCTIONS
func Record(t testing.TB, feature, fixture, name string)
Record records the fixture case result after its cleanup has completed.
github.com/polyspec/crudui/packages/validator/go/validator/text
package text // import "github.com/polyspec/crudui/packages/validator/go/validator/text"
Package text checks input text (docs/spec/input-text.md). Every string and
object member name in a specification, a composition file and caller data is
valid UTF-8, the Go form of a sequence of Unicode scalar values. Invalid text is
rejected before any other check of an operation; it is never replaced and never
passed on.
CONSTANTS
const LimitMessage = "Recursive or excessively nested value"
LimitMessage is the message of every value beyond the limits of a value.
const Message = "Text must be Unicode scalar values"
Message is the message of every input text failure.
const NestingLimit = 512
NestingLimit is the number of levels of arrays and objects a value may nest,
the value itself included.
const NodeLimit = 1000000
NodeLimit is the number of nodes a value may hold: every array, object,
string, number, boolean and null, the value itself included.
FUNCTIONS
func CheckInputs(inputs ...Input) string
CheckInputs checks named caller values in order and returns the failure
message of the first invalid text, which names the value and its path,
or of the first value beyond its limits, which names the value; the empty
string when every value passes.
func CheckSpecification(spec any, files any) (*compose.ComposeLoadError, string)
CheckSpecification checks a specification and then the composition files
an operation reads. Invalid text is the INVALID_TEXT load failure located
at its specification path, or at the file name followed by its path in the
file; an invalid file name is located at the empty path. A value beyond its
limits is an input failure, whose message naming spec or files is the second
result.
func CheckedLoader(loader compose.FileLoader) compose.FileLoader
CheckedLoader returns a loader that checks each document it loads, located
as a composition file.
func LoadFailure(trace []string) *compose.ComposeLoadError
LoadFailure returns the INVALID_TEXT load failure at trace.
func Size(value any) (levels, nodes int)
Size returns the levels of lists and objects that value nests, value itself
the first, and the nodes it holds, counted as the value limits count them.
TYPES
type Failure struct {
Path []string
Limit bool
}
Failure is the first failure in a value: invalid text at Path, or, when
Limit is true, a value beyond its limits.
func ValueFailure(value any) *Failure
ValueFailure returns the first failure in value, walked as the tree it
denotes: list items in index order; for an object, every member name first,
then the members in code point order of their names, which is the byte
order of valid UTF-8. Invalid text is located at the path of its string,
or of the object whose member name is invalid. A value that nests more than
NestingLimit levels or holds more than NodeLimit nodes, which includes every
value that contains itself, is beyond its limits where the walk passes them.
The result is nil when there is no failure.
type Input struct {
// Name is the documented argument name, such as data or options.basepath.
Name string
// Value is the caller value.
Value any
}
Input is one named caller value of an operation.
package validate // import "github.com/polyspec/crudui/packages/validator/go/validator/validate"
Package validate is the Go model form validator (SPEC §2 G5 → §3 → §2 G1) — the
THIRD pass of the model pipeline.
It consumes a model field model (the validate / design / behavior / options role
slots) AFTER the compose pass (validator-go/validator/compose) has expanded $ref
/ $patch into a single spec. It does NOT re-implement the expression engine —
it CALLS the existing engine (validator-go/validator/expr) for conditional rule
values. The rule semantics mirror the JS reference (validator-ts/src/rules) byte
for byte; the shared 4-language fixture tests/fixtures/validate/cases.json is
the single truth, generated from the JS engine.
Pipeline (SPEC):
(1) compose — ComposeProperties / ComposeSpec (compose/) runs BEFORE this
engine (Validate in index.go). An unresolved $ref is a *ComposeLoadError
there (never valid:true).
(2) field traversal — recurse properties; group nesting, multiple arrays
(items.i), object-key multiple (sorted keys, items.__uid__). Data order is
preserved by form state and rendering; sorted order is validation errors.
(3) validate-slot evaluation — per field, walk the validate slot in
declaration order; for each rule evaluate its (possibly conditional) value
to an effective param (expression / condition map); skip when false/null;
else run the rule. type:number runs an implicit number rule first when no
explicit number rule is present.
(4) error collection — first error per field stops that field; collect into a
flat []ValidationError; valid = len(errors) == 0.
Go ≥ 1.18: interface{} is spelled any. No eval.
FUNCTIONS
func CanonicalNumber(v float64) string
CanonicalNumber writes a finite number as ECMAScript
Number.prototype.toString does: the shortest digits that read back as the
same double, laid out in plain notation for magnitudes from 10^-6 up to
10^21 and in exponent notation otherwise; negative zero is "0". A nonfinite
number, which has no canonical text, is written as ECMAScript writes it
(NaN, Infinity) for messages and keys.
func GroupSourceFailure(field *compose.OMap, path string, root *compose.OMap) string
GroupSourceFailure returns the message of the first rule that the group
source of field, declared at path, breaks under the root properties, or ""
when it breaks none. The members of items are checked in declaration order,
then the field, from, the type of its field, label and the type of its
field. A field whose items is not a group source has no failure.
func GroupSourceItems(items any) (*compose.OMap, bool)
GroupSourceItems returns the items of a field as a group source, and whether
the items are one: an object with the member from.
func IsStepMultiple(value, step float64) bool
IsStepMultiple reports whether value is an integer multiple of a positive
step counted from 0, decided exactly as the step rule decides it.
func NumberRange(param any) (float64, float64, bool)
NumberRange returns the minimum and maximum of a literal range parameter:
two finite numbers with the minimum not above the maximum.
func StepSize(param any) (float64, bool)
StepSize returns a literal step parameter: a finite number above 0.
TYPES
type FileSet map[string]*compose.OMap
FileSet is a virtual file set $ref resolves against ({ key: doc }).
type FormInputError struct {
// Message names the rule and the data path.
Message string
}
FormInputError reports submitted form data whose shape does not match the
specification. It is a validation failure without a validation result,
distinct from a composition load failure.
func (e *FormInputError) Code() string
Code returns the stable machine-readable code shared with the form runtime.
func (e *FormInputError) Error() string
Error implements the error interface.
type GroupChoice struct {
Value string
Label any
}
GroupChoice is one choice of a group source: the canonical text of its
value and its label. A literal choice keeps its declared label (CUSC-089);
a row's label is the text of its label field, or the value when the source
declares no label or the row gives none.
func GroupChoices(source *compose.OMap, data any) ([]GroupChoice, error)
GroupChoices returns the choices of a checked group source for the root
data: its literal choices in written order (CUSC-089), then one per row of
the group in data order whose value is a string or a finite number with a
non-empty canonical text, each value once. Data is an ordered object or a
plain map; a plain map has no member order, so its rows are read in sorted
key order. A row whose value is a literal choice returns a FormInputError
with its path and value.
type ListValidationResult struct {
// Valid reports whether Errors is empty.
Valid bool `json:"valid"`
// Errors is the flat list of failures.
Errors []ValidationError `json:"errors"`
}
ListValidationResult is the outcome of checking a list or detail
declaration: valid plus the collected errors.
func ValidateDetail(spec *compose.OMap, opts Options) (ListValidationResult, error)
ValidateDetail composes and forbidden-scans a detail-spec. It returns a
clean ListValidationResult (valid:true, no errors) when the detail composes
and scans clean. An unresolved composition or a forbidden meta key returns a
*compose.ComposeLoadError.
func ValidateDetailJSON(specJSON []byte, filesJSON map[string][]byte, basepath string) (ListValidationResult, error)
ValidateDetailJSON decodes a raw JSON detail-spec and the optional { key:
rawJSON } file set, then runs ValidateDetail. A detail carries no data.
func ValidateList(spec *compose.OMap, opts Options) (ListValidationResult, error)
ValidateList runs composition and forbidden-key scanning over a list-spec
given as the engine value model (*compose.OMap). It returns a clean
ListValidationResult (valid:true, no errors) when the list composes and
scans clean — a list carries no rows, so there is no DATA pass to produce
field errors. An unresolved composition or a forbidden meta key at any depth
returns a *compose.ComposeLoadError (the caller distinguishes a LOAD failure
from a validation result), identical to the form path.
func ValidateListJSON(specJSON []byte, filesJSON map[string][]byte, basepath string) (ListValidationResult, error)
ValidateListJSON decodes a raw JSON list-spec (compose.DecodeOrdered so
declaration order survives) and the optional { key: rawJSON } file set,
then runs ValidateList. Mirrors ValidateJSON (the form entry) for the CLI
/ gateway subprocess wire. A list carries no data, so there is no data
argument.
type Options struct {
// Files is the virtual file set for $ref resolution (default: empty).
Files FileSet
// Basepath is the basepath for relative $ref resolution.
Basepath string
}
Options configures a model validation run.
type ValidationError struct {
// Path is the dot-joined full path to the field (items.0.name, rows.__uid__.v).
Path string `json:"path"`
// Field is the last path segment (the field name).
Field string `json:"field"`
// Rule is the failed rule name, the invoked key verbatim (pattern vs match).
// An implicit type:number failure reports "number".
Rule string `json:"rule"`
// Message is the display message: field messages[rule] override → rule default
// (with {0}/{1} substituted) → "Validation failed." fallback.
Message string `json:"message"`
// Value is the failed value (the whole array for array-level rules).
Value any `json:"value"`
}
ValidationError is one validation failure. Field shape (path / field /
rule / message / value) is identical in every runtime so the 4-language
idempotence comparison (SPEC G-B) holds with the same (path, rule, message).
type ValidationResult struct {
// Valid reports whether Errors is empty.
Valid bool `json:"valid"`
// Errors is the flat list of failures in traversal order.
Errors []ValidationError `json:"errors"`
// Hidden lists the data paths of the fields whose design.show resolves to
// false, in declaration order (validation-rules.md, "Evaluation").
Hidden []string `json:"hidden"`
}
ValidationResult is the outcome of validating data: valid, the collected
errors and the hidden paths.
func Validate(spec *compose.OMap, data any, opts Options) (ValidationResult, error)
Validate validates data against a model spec given as the engine value
model (*compose.OMap, decoded with compose.DecodeOrdered). The spec may
carry $ref / $patch; they are expanded first. An unresolved composition
returns a *compose.ComposeLoadError, and data with the wrong shape returns a
*FormInputError; neither produces a validation result. G5 → §3 → §2 G1.
func ValidateJSON(specJSON []byte, dataJSON []byte, filesJSON map[string][]byte, basepath string) (ValidationResult, error)
ValidateJSON is a convenience wrapper that decodes a raw JSON spec
and raw JSON data, then runs Validate. The spec is decoded with
compose.DecodeOrdered so declaration order survives; data is decoded with
compose.DecodeOrdered into plain values (objects → map[string]any, arrays
→ []any, numbers → float64). Both keep text as written, so invalid text
reaches the input text check. Empty data bytes validate an empty object;
any decoded data value is passed to Validate unchanged. Files is a { key:
rawJSON } map.
type Validator struct {
// Has unexported fields.
}
Validator validates data against a composed model spec. Construct via
NewValidator from a composed properties OMap.
func NewValidator(properties *compose.OMap) (*Validator, error)
NewValidator builds a validator from a COMPOSED properties OMap (composition
keys already eliminated by the compose pass) and checks its declared rule
names and parameters. A rule name that is not registered, or a parameter
outside its rule's definition, returns a *compose.ComposeLoadError
(UNKNOWN_RULE, INVALID_RULE_PARAMETER or INVALID_RULE_PATTERN) at the
field's declaration path.
func (v *Validator) Validate(data any) (ValidationResult, error)
Validate validates data against the composed model spec. Root, group and
repeated data with the wrong shape return a *FormInputError and no result.
A rule parameter a condition selects outside its definition returns a
*compose.ComposeLoadError and no result.
package generator // import "github.com/polyspec/crudui/packages/generator/go"
Package generator compiles, binds and renders CRUDUI forms and lists in Go.
CONSTANTS
const HistoryLimit = 100
HistoryLimit is the number of records an undo history keeps in each
direction (CURT-062).
FUNCTIONS
func CanRedo[T any](history History[T]) bool
CanRedo reports whether the history has a record to restore in the forward
direction (CURT-078).
func CanUndo[T any](history History[T]) bool
CanUndo reports whether the history has a record to restore (CURT-078).
func CheckBindText(template any, data *Object, options BindOptions) error
CheckBindText runs the input text checks BindForm, BindButtons and NewForm
run first: the template, the data, then the options in code point order
of their names. template is a *FormTemplate or its decoded JSON object;
a caller that decodes a template from JSON text checks the decoded object
before decoding it into a FormTemplate, whose JSON encoding replaces invalid
text.
func CollapsibleRows(nodes []*Object) []string
CollapsibleRows returns the row paths {collection}.{key} of every
collapsible row of the evaluated nodes, depth first in node order.
func CreateRowKey() (string, error)
CreateRowKey creates a row key containing 13 random hexadecimal characters.
func DecodeJSON(data []byte) (any, error)
DecodeJSON preserves object order and rejects malformed or multiple
documents.
func FormButtonsHTML(buttons any) (string, error)
FormButtonsHTML renders evaluated buttons as the markup every renderer
inserts into the form footer. The buttons are a list: a Go slice of button
objects, or the list of a decoded JSON value, and nil is the empty list.
Each button must be an object whose tag is a or button, whose text is a
string and whose attrs object has only the named button attributes with
string values; other members are ignored.
func RenderDetail(spec *Object, record any, options DetailOptions) (string, error)
RenderDetail renders one read-only detail: the image preload links,
the actions when the detail has any, and the definition list.
func RenderForm(form *Form, options any) (string, error)
RenderForm renders one instance as the complete form without changing its
template or data (form-runtime.md, "Complete form"). options is nil or an
object with the optional members action, hidden, formErrors and errors;
nil writes the crudui-form block alone.
func RenderList(spec *Object, rows any, options ListOptions) (string, error)
RenderList renders a table or card list with image resource hints in
first-use order. rows is a []*Object or a decoded JSON array of objects.
func SequenceRowKey(sequence any) (string, error)
SequenceRowKey converts a nonnegative sequence of at most 13 digits to a
scoped row key.
TYPES
type ActionInput struct {
Action string
Path string
Key string
}
ActionInput is the values a button declares: its action, collection path and
row key (CURT-081). An empty member is absent.
type ActionResult struct {
Focus *FocusTarget
}
ActionResult is the outcome of an action that ran; Focus is nil when focus
stays on the current control.
func RunAction(form *Form, target ActionTarget) (*ActionResult, error)
RunAction runs a resolved action on the form. A nil result means the action
had no result: its target lacks the path, or the key that the action needs,
and nothing changes. A failing operation returns its error and leaves the
form unchanged (CURT-090).
type ActionTarget struct {
Name string
Path string
Key string
}
ActionTarget is the operation, collection path and row key that an action
button targets; Path and Key are empty when the button declares none.
func ResolveAction(input ActionInput) (ActionTarget, bool)
ResolveAction resolves the target of the values a button declares; ok is
false when the action is none of the eleven form actions.
type AddRowOptions struct {
Key string
AfterKey string
Value any
ValueProvided bool
}
AddRowOptions preserves explicit null through ValueProvided.
type BindOptions struct {
// IDPrefix defaults to crudui.
IDPrefix any
// Language defaults to ko and must be ko, en, ja or zh, so an empty string is rejected.
Language any
// KeyPrefix defaults to the template prefix; an empty string removes it.
KeyPrefix any
// Unsupported is throw (default) or marker, which renders unsupported fields as markers.
Unsupported any
// Collapsed lists the row paths {collection}.{key} rendered collapsed; the other rows are expanded.
Collapsed []string
}
BindOptions selects control IDs, content and interface language, form-name
prefix and unsupported-field handling. Each option is nil for its default;
decoded JSON of another type or value is rejected.
type CompileOptions struct {
Files map[string]*Object
Loader compose.FileLoader
Basepath string
KeyPrefix string
KeyPrefixProvided bool
}
CompileOptions supplies composition files and the optional form-name prefix.
type DetailOptions = ListOptions
DetailOptions supplies composition, display language and caller-owned data
options. The list-only Layout, Page and Total are ignored; detail output is
always a read-only definition list.
type FieldTemplate struct {
Name string `json:"name"`
Spec *Object `json:"spec"`
Children []FieldTemplate `json:"children"`
}
FieldTemplate stores one resolved field specification and its ordered
children.
type FocusTarget struct {
Path string
Key string
}
FocusTarget is the row that receives focus after an action; Key is empty
when the collection has no rows left.
type Form struct {
// Has unexported fields.
}
Form owns an independent record, compiled template and current field models.
func NewForm(template *FormTemplate, data *Object, options BindOptions) (*Form, error)
NewForm copies the template and record, applies missing defaults and binds
fields.
func (f *Form) AddRow(path string, options AddRowOptions) (string, error)
AddRow inserts a default or supplied row in the selected repeated
collection.
func (f *Form) CanRedo() bool
CanRedo reports whether Redo reapplies an undone record (CURT-061).
func (f *Form) CanUndo() bool
CanUndo reports whether Undo restores an earlier record (CURT-061).
func (f *Form) CopyRow(path, key string, options AddRowOptions) (string, error)
CopyRow copies a row and generates fresh keys for its repeated descendants.
func (f *Form) Fields() []*Object
Fields returns deep copies of the current field models.
func (f *Form) GetData() *Object
GetData returns a deep copy of the current ordered record.
func (f *Form) GetValue(path string) (any, error)
GetValue returns a detached value at a record path; absent values return
nil.
func (f *Form) MoveRow(path, key string, index int) error
MoveRow moves an existing row to a zero-based collection position.
func (f *Form) Redo() error
Redo reapplies the most recently undone data change (CURT-012); it fails
when nothing can be redone.
func (f *Form) RekeyRow(path, oldKey, newKey string) error
RekeyRow replaces a row key without changing its value or collection
position.
func (f *Form) RemoveRow(path, key string) error
RemoveRow removes a row when the collection minimum permits removal.
func (f *Form) Revision() uint64
Revision returns the number of successful instance mutations.
func (f *Form) SetAllExpanded(expanded bool) error
SetAllExpanded expands every row, or collapses every collapsible row;
the record and the revision do not change.
func (f *Form) SetData(data *Object) error
SetData replaces the complete record and rebinds fields atomically.
func (f *Form) SetValue(path string, value any) error
SetValue replaces one path value and rebinds fields atomically.
func (f *Form) Template() (*FormTemplate, error)
Template returns a deep copy of the reusable form structure.
func (f *Form) ToggleRow(path, key string) error
ToggleRow expands a collapsed row or collapses an expanded one; the record
and the revision do not change.
func (f *Form) Undo() error
Undo restores the record before the last data change and makes the reverted
record redoable (CURT-011); it fails when nothing can be undone.
type FormTemplate struct {
Kind string `json:"kind"`
KeyPrefix string `json:"keyPrefix,omitempty"`
Fields []FieldTemplate `json:"fields"`
// Buttons lists the form buttons in declaration order; one submit button when the spec declares none.
Buttons []*Object `json:"buttons"`
// Action is the submission target declared by the spec, kept unchanged in the template.
Action *Object `json:"action,omitempty"`
// Description is the root description content as the spec declares it, translated when the
// form is rendered. It is a template member when the root declares description.
Description any `json:"description,omitempty"`
// Has unexported fields.
}
FormTemplate stores reusable structure without record data.
func CompileForm(spec *Object, options CompileOptions) (*FormTemplate, error)
CompileForm resolves field composition without binding record data.
func (t FormTemplate) MarshalJSON() ([]byte, error)
MarshalJSON retains specification order, an explicitly empty form-name
prefix and a declared null description.
func (t *FormTemplate) UnmarshalJSON(data []byte) error
UnmarshalJSON reads the template in specification member order. A value that
is not exactly the compiled shape is rejected: the template kind, a field
list, a button object list, an optional string keyPrefix, an optional object
action, an optional description of any value and no other member.
type History[T any] struct {
// Entries are the earlier records, at most HistoryLimit.
Entries []T
// RedoEntries are the records that redo restores, at most HistoryLimit.
RedoEntries []T
// LastPath is the path of the last change when it was a value edit, and empty otherwise.
LastPath string
}
History is the undo history of a form: the records before earlier changes
and the records after undone changes, oldest first. The functions below take
and return it by value and never change their arguments.
func EmptyHistory[T any]() History[T]
EmptyHistory returns the history with no record (CURT-076).
func RecordChange[T any](history History[T], previous T, path string) History[T]
RecordChange appends the record before a change to the entries unless path
equals the path of the last value edit, keeps the last HistoryLimit entries,
clears the redo entries and sets the path of the history to path (CURT-077,
CURT-063).
type HistoryResult[T any] struct {
History History[T]
Value T
}
HistoryResult is the history after an undo or a redo and the record that it
restored.
func RedoChange[T any](history History[T], current T) (HistoryResult[T], error)
RedoChange returns the history with current appended to the entries and
without its last redo entry, and that redo entry as the record; it fails
when the history has no redo entry (CURT-080).
func UndoChange[T any](history History[T], current T) (HistoryResult[T], error)
UndoChange returns the history without its last entry, with current appended
to the redo entries, and the last entry as the record; it fails when the
history has no entry (CURT-079).
type ListOptions struct {
Language string
// Data is nil or an *Object; any other value fails with "List context must be an object".
Data any
// Page is nil (none) or a number holding an integer from 1 to 9007199254740991;
// any other value fails with "List page must be a positive integer".
Page any
// Total is nil (none) or a number holding an integer from 0 to 9007199254740991;
// any other value fails with "List total must be a nonnegative integer".
Total any
Files map[string]*Object
Loader compose.FileLoader
Basepath string
// Layout is nil (table) or the string "table" or "card"; any other value fails.
Layout any
}
ListOptions supplies composition, display language, layout and caller-owned
pagination data. Data, Page, Total and Layout take decoded values unchanged
so their type is checked like every runtime.
type Object = compose.OMap
Object preserves JSON member order. Arrays use []any and null uses nil.
func BindButtons(template *FormTemplate, data *Object, options BindOptions) ([]*Object, error)
BindButtons evaluates the template's buttons for a record in declaration
order. Each button is an object with type, tag, text and attrs; attrs keeps
the output order type, class, style, name, value, href, onclick. Only the
Language option applies.
func BindForm(template *FormTemplate, data *Object, options BindOptions) ([]*Object, error)
BindForm creates the node models of the form grammar without changing the
template or record.
func BuildDetail(spec *Object, record any, options DetailOptions) (*Object, error)
BuildDetail creates a read-only detail model from one supplied record.
Field composition, design evaluation and cell formatting are shared with
BuildList. record is an *Object or a decoded JSON value; any other value is
no object (CUDF-033).
func BuildList(spec *Object, rows any, options ListOptions) (*Object, error)
BuildList creates a complete list model from the specification and ordered
records. rows is a []*Object or a decoded JSON array of objects.
func BuildOutline(nodes []*Object) []*Object
BuildOutline builds the structure map: the form's rows, nested as in the
form. Collections, counts and empty collections are not rows.
func NewObject(pairs ...any) *Object
NewObject creates an ordered object from alternating string keys and values.
type UnsupportedFieldTypeError struct {
Type string
Path string
}
UnsupportedFieldTypeError identifies a field type that has no supported
widget.
func (e *UnsupportedFieldTypeError) Code() string
Code returns the shared unsupported-field error code.
func (e *UnsupportedFieldTypeError) Error() string
Error returns the unsupported type and its structural field path.
type ViewState struct {
// Collapsed lists the collapsed row paths.
Collapsed []string
}
ViewState is the view state of a form kept outside the record data:
the row paths {collection}.{key} rendered collapsed, in the order the rows
were collapsed, without duplicates. It is never submitted with the data.
func InitialView() ViewState
InitialView returns the view with no collapsed row. Replacing the record
returns to it.
func RekeyRowView(view ViewState, oldPath, newPath string) ViewState
RekeyRowView moves the collapsed state of a rekeyed row and of the rows
inside it to the new row path.
func RemoveRowView(view ViewState, rowPath string) ViewState
RemoveRowView drops the collapsed state of a removed row and of the rows
inside it.
func SetAllExpandedView(nodes []*Object, expanded bool) ViewState
SetAllExpandedView returns the view with no collapsed row when expanded is
true, and otherwise the view that collapses every collapsible row of the
evaluated nodes.
func ToggleRowView(view ViewState, rowPath string) ViewState
ToggleRowView expands a collapsed row or collapses an expanded one,
appending a collapsed row last.