Documentation
¶
Overview ¶
Package formbuilder is the GoFastr form-builder plugin: an authoring tool for the framework itself, not another content editor.
Every other plugin in this repo edits content — a document, a diagram, a chart spec, a table view. This one produces a FORM SCHEMA (formbuilder-v1) that GoFastr's own ui.Form renders and that the HOST re-validates on every submit: a form designed in an opaque-origin sandboxed iframe is enforced by the server with no client trust anywhere in the path. That round trip — design in the cage, enforcement in Go — is the entire argument; the demo's live-form route closes it visibly.
The canonical doc is the schema and it is DATA ONLY: {version, fields[]}, never markup (see schema.go — a label containing "<" is refused at save). The Go side validates every save and refuses bad schemas with specific 400 codes: unknown field type, duplicate/empty/invalid name, malformed rule, unknown version, markup. A schema that somehow gets past the frame still gets refused here.
Capabilities: document:read, document:write, theme:read (all always-on; there are no optional grants). pluginhost.Allow is a capability gate, NOT authentication: it passes for anonymous callers, so POST /save must be documented as requiring the host to check the session in its own handler — the demo's WithDevGrantAll skips the gate entirely and MUST NOT survive into a production mount. See docs/formbuilder.md.
Index ¶
- Constants
- Variables
- func DefaultCapabilities() []string
- func Mount(cfg MountConfig) render.HTML
- func RenderForm(action string, doc Doc, values url.Values, errs ui.FieldErrors) render.HTML
- func UIHostOption() uihost.Option
- func ValidateDoc(d *Doc) error
- func ValidateValues(doc Doc, vals url.Values) ui.FieldErrors
- type Doc
- type Field
- type MountConfig
- type Option
- type Plugin
- type Rules
- type SaveRequest
- type SchemaError
Constants ¶
const ( Name = "formbuilder" Version = "0.1.0" RoutePrefix = "/__gofastr/plugin/formbuilder" BuilderHTMLURL = RoutePrefix + "/builder.html" BuilderJSURL = RoutePrefix + "/builder.js" BuilderCSSURL = RoutePrefix + "/builder.css" AdapterScriptURL = RoutePrefix + "/adapter.js" SaveURL = RoutePrefix + "/save" DemoURL = "/formbuilder" LiveURL = "/formbuilder/live" SchemaVersion = "formbuilder-v1" )
Identity and route constants. Both this plugin and host/adapter.js hard-code these exactly (protocol-v1.md §2/§10). The design demo lives at /formbuilder; the live-form proof route at /formbuilder/live.
These mirror the formbuilder row in plugins.json; internal/registry tests pin Name + RoutePrefix against that row, so they MUST NOT drift.
const ( ErrBadJSON = "E_BAD_JSON" ErrUnknownVersion = "E_UNKNOWN_VERSION" ErrTooManyFields = "E_TOO_MANY_FIELDS" ErrUnknownFieldType = "E_UNKNOWN_FIELD_TYPE" ErrEmptyName = "E_EMPTY_NAME" ErrInvalidName = "E_INVALID_NAME" ErrDuplicateName = "E_DUPLICATE_NAME" ErrLabelTooLong = "E_LABEL_TOO_LONG" ErrHelpTooLong = "E_HELP_TOO_LONG" ErrMarkup = "E_MARKUP" ErrBadSelect = "E_BAD_SELECT" ErrBadRule = "E_BAD_RULE" )
Error codes, so tests and docs pin the exact vocabulary.
Variables ¶
var FieldTypes = []string{"text", "email", "number", "textarea", "select", "checkbox", "date"}
FieldTypes is the closed set of field types a schema may declare. Adding one is a schema change: bump the version and teach render.go + the frame.
Functions ¶
func DefaultCapabilities ¶
func DefaultCapabilities() []string
DefaultCapabilities is the always-on grant set: reading and writing the schema document plus theme bridging. document:write gates POST /save.
func Mount ¶
func Mount(cfg MountConfig) render.HTML
Mount renders the generic mount marker plus the hidden input the adapter syncs on docChanged. Drop it into a form. All interpolated values are HTML-escaped inside pluginhost.MountMarker.
func RenderForm ¶
RenderForm renders doc as a complete ui.Form posting to action. values pre-fills controls (a re-render after a refused submit keeps what the user typed); errs maps field names to messages and wires the per-field error chrome plus the form-level error summary.
func UIHostOption ¶
UIHostOption injects the platform broker, then this plugin's adapter (the adapter registers with the broker the former defines).
func ValidateDoc ¶
ValidateDoc enforces every formbuilder-v1 invariant and NORMALISES the doc in place (version stamped, empty labels defaulted to the name). A doc that returns nil is safe to persist and safe to render.
func ValidateValues ¶
func ValidateValues(doc Doc, vals url.Values) ui.FieldErrors
ValidateValues re-derives every rule from the schema and applies it to the submitted form values, server-side. The browser was not consulted; neither was the frame. Returned errors are ui.FieldErrors so they drop straight into the re-rendered form.
Types ¶
type Doc ¶
type Doc struct {
// Version is the schema version of the doc itself. Empty is treated as
// the current version on save (the frame does not always stamp it); any
// OTHER value is refused with E_UNKNOWN_VERSION — a saved schema has to
// outlive the plugin that wrote it, so an unknown future version must
// fail loudly, not silently degrade.
Version string `json:"version"`
Fields []Field `json:"fields"`
}
Doc is the canonical formbuilder-v1 document: the form schema as pure data.
type Field ¶
type Field struct {
Type string `json:"type"`
Name string `json:"name"`
Label string `json:"label"`
Required bool `json:"required"`
Help string `json:"help"`
// Options is the closed value set; select fields only.
Options []string `json:"options,omitempty"`
Rules Rules `json:"rules,omitempty"`
}
Field is one designed form field.
type MountConfig ¶
type MountConfig struct {
// DocID is the persistence key for the schema doc. Defaults to "demo".
DocID string
// Field is the hidden input name the adapter mirrors the schema JSON
// into, so a normal form POST round-trips it. Defaults to
// "formbuilder_doc".
Field string
// Doc is optional initial schema JSON server-rendered into the marker
// (data-fui-plugin-doc) for reload round-trip.
Doc string
// MinHeight is the initial iframe height. Defaults to 560px.
MinHeight string
}
MountConfig configures Mount.
type Option ¶
type Option func(*Plugin)
Option configures a Plugin.
func WithCapabilities ¶
WithCapabilities overrides the grant set advertised to the builder. Default: DefaultCapabilities. Grants are matched with the framework's wildcard scope grammar at runtime.
func WithDemoDoc ¶
WithDemoDoc sets the schema the demo page mounts when nothing has been saved yet — the starting canvas the visitor edits.
func WithDemoPage ¶
func WithDemoPage() Option
WithDemoPage registers the self-contained themed demo page at DemoURL AND the live-form proof route at LiveURL (GET renders the saved schema through ui.Form; POST validates in Go and answers).
func WithDemoRoute ¶
WithDemoRoute overrides where WithDemoPage mounts the design demo (default DemoURL, "/formbuilder"). The live route stays at LiveURL.
func WithDevGrantAll ¶
func WithDevGrantAll() Option
WithDevGrantAll short-circuits the capability gate (demo / tests only). It bypasses the gate on POST /save; the route still fails closed on an unwired handler.
func WithSaveHandler ¶
func WithSaveHandler(fn func(ctx context.Context, req SaveRequest) error) Option
WithSaveHandler overrides the schema persistence hook. The default stores the validated canonical doc JSON in an in-memory map keyed by DocID. This is the host's chance to authorize the write against the real session: pluginhost.Allow is a capability gate, not authentication.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the form-builder plugin. It implements framework.Plugin and mirrors the mermaid/datagrid shape: opaque-origin sandboxed iframe, protocol v1 over postMessage, go:embed'd frame bundle, capability gate, one host-side RPC route (/save). The difference is what the doc IS: not content to display but a schema the server consumes and enforces.
func (*Plugin) Capabilities ¶
Capabilities returns the grant set this plugin advertises to the builder.
func (*Plugin) LoadDoc ¶
LoadDoc returns the last-saved schema JSON for docID (demo round-trip). ok is false when the doc has never been saved.
func (*Plugin) Manifest ¶
func (p *Plugin) Manifest() pluginhost.Manifest
type Rules ¶
type Rules struct {
MinLength *float64 `json:"minLength,omitempty"`
MaxLength *float64 `json:"maxLength,omitempty"`
Min *float64 `json:"min,omitempty"`
Max *float64 `json:"max,omitempty"`
// Pattern is a regexp, compiled with Go's RE2 at save time. The server
// re-compiles it on every validation, so the frame and the server cannot
// disagree about what it means.
Pattern string `json:"pattern,omitempty"`
}
Rules are the validation constraints. Length/range bounds ride as *float64 (not *int) so a hostile "minLength": 2.5 decodes and is REFUSED as a bad rule, rather than dying as a JSON unmarshal error that reads as a transport problem.
type SaveRequest ¶
SaveRequest is the schema persist signal handed to the save handler.
type SchemaError ¶
SchemaError is a validation refusal: a stable machine-readable code plus a human sentence. The code is what tests (and the frame's status line) branch on; the message is what a person reads.
func (*SchemaError) Error ¶
func (e *SchemaError) Error() string