formdecision

package
v0.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package formdecision gives a form field its options from a deployed DMN decision: a select, radio group, checklist or tag list whose choices are the list a decision answers with — typically a boxed list (DMN §10.3.2.7) that the people who own the list maintain in the decision editor, versioned and published like any other decision (ADR-0511). The decision's inputs are bound to other fields of the form, or fixed, so a choice in one field decides what the next one offers: a group, then its members; a country, then its regions.

The decision is evaluated when the form is shown and whenever a field its inputs are bound to changes, never stored with it: a change to the list is what the next person to open the form sees. Nothing here is durable. An evaluation is a read of a compiled model the registry already holds; it reaches no log, no state store and no processor, and the only thing that becomes a fact is the value the person submits — which is checked against the list once more before the task completes.

**What a request may say** is the values of the fields the stored form binds, and nothing else: which decision, which input each value is, and which inputs are fixed are read from the form, and a request naming anything more is refused. A value bound from a field that offers fixed choices — its own static options, or the list of a decision field before it — must be one of those choices, so a field cannot be used to ask a decision about values the form never offers. The form editor's route evaluates a decision with inputs of the author's choosing, and is a modeler's, who may already try any model (POST /api/v1/decisions/evaluate).

**This area owns no state**, like formlookup: the registry it reads is the server's, and the server's closures observe the single writer (I3).

Index

Constants

View Source
const MaxOptions = 1000

MaxOptions bounds the options one decision gives a field. A choice among more than this is not made from a list a person scrolls through; it wants a search, which is what a directory field is (ADR-0473). A list over the bound is refused rather than cut, because a cut list would hide entries without saying which.

View Source
const Member = "atlasDecision"

Member is the component member that makes a field a decision field.

View Source
const Version = 1

Version is the one version of the block this server reads. A form written for a newer one is refused at save with that reason, rather than half-understood.

Variables

View Source
var Codes = []string{
	"not_deployed", "unbound_input", "evaluation_failed", "not_a_list",
	"not_found", "invalid_query", "invalid_value", "invalid_configuration",
}

Codes are the machine-readable codes a refusal answers with, beside its message.

View Source
var FieldTypes = []string{"select", "radio", "checklist", "taglist"}

FieldTypes are the form-js fields that take their options from a source. A select and a radio group submit one value; a checklist and a tag list submit a list.

Functions

func Check

func Check(schema []byte) error

Check is what a form save asks: may this schema be stored?

Types

type Binding

type Binding struct {
	Name  string
	Field string
	Value any
}

Binding is one input of a decision field, checked: the input's name, and either the key of the field it is bound to or its fixed value.

func (Binding) Fixed

func (b Binding) Fixed() bool

Fixed reports whether the input has a fixed value rather than a field's.

type Config

type Config struct {
	// Decision resolves a decision id to its newest decision deployment — what it is,
	// and the key it is evaluated under (dmn.Registry.LatestDeployedDecision).
	Decision func(id string) (dmn.DeployedDecision, uint64, bool)
	// Decisions lists every decision that has a decision deployment.
	Decisions func() []dmn.DeployedDecision
	// Evaluate evaluates the decision from the deployment under key against inputs.
	Evaluate func(ctx context.Context, key uint64, id string, in map[string]any) (map[string]any, error)
	// MaxBody bounds a request's body: the server's Request budget.
	MaxBody int64
}

Config is the server's half of this area.

type Error

type Error struct {
	Status  int
	Code    string
	Message string
}

Error is a refusal: a status, a code a client can branch on, and a message a person can read.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Write

func (e *Error) Write(w http.ResponseWriter)

Write answers the refusal in the API's error envelope, with its code.

type Field

type Field struct {
	Key        string
	Label      string
	Type       string
	DecisionID string
	Inputs     []Binding
	// Multi is true for a field that submits a list of values.
	Multi bool
	// Path is where the field's value sits in what the form submits: the paths of the
	// groups and dynamic lists around it, then its own key, both split at their dots as
	// form-js splits them.
	Path        []Step
	Required    bool
	Disabled    bool
	Conditional bool
}

Field is one decision field of a form, checked.

func (Field) BoundFields

func (f Field) BoundFields() bool

BoundFields reports whether any input of the field is bound to another field — which is what makes its options depend on what the person chooses.

type Form

type Form struct {
	Fields []Field
	// contains filtered or unexported fields
}

Form is what a schema says about its decision fields and the fields they are bound to.

func Parse

func Parse(schema []byte) (*Form, error)

Parse reads a form-js schema's decision fields and checks every one of them. A schema without any parses to none; a malformed atlasDecision is refused with the field and the reason.

What it does not check is whether the decision is deployed, which inputs it takes and whether it answers with a list. That is the server's state, not the form's: it changes after the form is saved, and an import may well bring a form in before the decision it names. The form editor shows it while the field is edited, and a form rendered against a decision that cannot answer says so in the field.

func (*Form) DecisionIDs

func (f *Form) DecisionIDs() []string

DecisionIDs are the decisions the form's fields name, each once, sorted.

func (*Form) Field

func (f *Form) Field(key string) (Field, bool)

Field finds the decision field with this key.

type Input

type Input struct {
	Name  string          `json:"name"`
	Field string          `json:"field,omitempty"`
	Value json.RawMessage `json:"value,omitempty"`
}

Input is one of the decision's inputs as a block states it: bound to another field of the form by its key, or a fixed value.

type Option

type Option struct {
	Label string `json:"label"`
	Value any    `json:"value"`
}

Option is one choice as the form shows it: what a person reads, and what the form submits when it is chosen.

func OptionsOf

func OptionsOf(result any) ([]Option, int, error)

OptionsOf turns a decision's result into options, the way form-js turns a select's `values` into options (normalizeOptionsData in @bpmn-io/form-js-viewer 1.24), so what the server checks a submission against is what the person was shown:

  • a string, number or boolean is both the label and the value; an empty string is no option;
  • a context with a non-empty string `label` and a `value` that is such a primitive or a non-empty context is taken as it is;
  • a context with only a primitive `value` is labelled with that value;
  • anything else — null, a list, a context without a usable value — is skipped and counted, so the editor can say how many entries the form will not show.

A result that is not a list at all is refused: the decision answers something else.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service serves a form's decision-backed options. Build it with New.

func New

func New(cfg Config) *Service

New builds the service.

func (*Service) CheckSubmission

func (s *Service) CheckSubmission(ctx context.Context, schema []byte, vars map[string]any, known func() map[string]any) *Error

CheckSubmission holds what a completion submits to each decision field's list, as the decision answers now for what the same submission chose in the fields its inputs are bound to, and returns the first refusal. Inside a dynamic list each row is held to the list its own row's choices give. A form without a decision field is not checked at all.

A bound field form-js did not submit — a disabled one — is read from known, which is what the form was opened with (the instance's variables for a task), and failing that from its default value. known is called at most once, and only when needed; it may be nil.

It checks that a value given is one the list offers, not that one is given: a required field is form-js's to insist on, as it is for every other field. Like the directory field's check (ADR-0473) it runs before the completion, not with it: the list may change between this check and the next step that reads the value.

func (*Service) FieldOptions

func (s *Service) FieldOptions(ctx context.Context, schema []byte, key string, values map[string]any) ([]Option, int, *Error)

FieldOptions answers the options of one decision field of a stored form, for the values of the fields its inputs are bound to. values may name those fields and the ones they depend on in turn, and nothing else.

func (*Service) HandleEvaluate

func (s *Service) HandleEvaluate(w http.ResponseWriter, r *http.Request)

HandleEvaluate is POST /api/v1/form-data/decisions/{decisionId}/options: the options a decision answers for inputs the author gives — the form editor's previews, for a form that may not be saved yet.

func (*Service) HandleList

func (s *Service) HandleList(w http.ResponseWriter, r *http.Request)

HandleList is GET /api/v1/form-data/decisions: the decisions a field's options can come from — every decision with a decision deployment — and the inputs each takes.

func (*Service) Options

func (s *Service) Options(ctx context.Context, id string, inputs map[string]any) ([]Option, int, *Error)

Options evaluates a decision's newest deployment against inputs and returns its list as options, and how many of its entries are no option a form can show. Every input the decision declares has to be given — a null is given, an absent one is not.

func (*Service) Resolve

func (s *Service) Resolve(ctx context.Context, schema []byte) []byte

Resolve writes each decision field's options into the form as the field's own `values` and drops its atlasDecision block, so the form renders as a plain form-js form wherever it is shown. It is for the pages nobody signs in to — a public start link, a public wizard step — where each such field would otherwise be one more request.

Only a field whose inputs are all fixed is written in: one bound to another field depends on what the visitor chooses there, and asks the page's route as the visitor chooses. A field whose decision cannot answer now is left as it was too: the viewer then says that its options could not be loaded, which an empty list would not. A form without a decision field, or one the save check would refuse, comes back unchanged.

func (*Service) WriteFieldOptions

func (s *Service) WriteFieldOptions(w http.ResponseWriter, r *http.Request, schema []byte, key string)

WriteFieldOptions answers a request for the options of one decision field of a stored form the server has already authorized the caller for: the console's, a public link's or a public wizard step's.

type Source

type Source struct {
	Version    int     `json:"version"`
	DecisionID string  `json:"decisionId"`
	Inputs     []Input `json:"inputs,omitempty"`
}

Source is the atlasDecision block as a form states it.

type Step

type Step struct {
	Name string
	Each bool
}

Step is one hop from the submitted variables to a field's value. Each marks a list whose every element holds the rest of the path — a dynamic list's rows.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL