standout_input/questionnaire/mod.rs
1//! Questionnaire answer sheets: render a prose questionnaire, collect
2//! answers interactively or from a document, and decode them by stable
3//! identity through one shared validation pipeline. Structs that derive
4//! `Questionnaire` lower to this same runtime model and fill themselves from
5//! validated [`Answers`] without a serde boundary.
6//!
7//! `standout-input` owns the reusable machinery: definition validation,
8//! deterministic rendering, parsing, collection adapters, shared field
9//! decoding and validation, derive-support traits, and diagnostics. The
10//! application owns its questionnaire definition, whole-form rules (a
11//! closure passed to [`Questionnaire::decode_answers_with`]), interactive
12//! flow, review, confirmation, and side effects.
13//!
14//! # Rendered format
15//!
16//! A rendered sheet is a preamble (format version, questionnaire ID,
17//! fingerprint) followed by numbered questions, each ending in a stable
18//! `<id:...>` tag. A line is a *question line* iff it ends with a tag as its
19//! last non-whitespace content — any trailing content, even a period,
20//! demotes it to prose. The answer is everything between a question line
21//! and the next one (or EOF); everything before the tag (numbering,
22//! wording, indentation, hint) is cosmetic and may be freely reworded.
23//! Declared defaults render pre-filled. One limitation is accepted by
24//! design: an answer that itself ends in a schema-valid tag reads as a
25//! question line — there is no escaping, only a warning-level diagnostic
26//! ([`RawAnswers::warnings`]) when answer text contains a stray `<id:`.
27//!
28//! # Nested and repeatable groups
29//!
30//! Alongside scalar fields, a questionnaire may declare [`Group`]s: nested
31//! sections answered once, or *repeatable* sections answered once per
32//! submitted item within [`Repeat`] bounds. Adding an item to a repeatable
33//! group is copy-the-block editing — duplicate one rendered block below the
34//! last and answer the copy; a group occurrence is counted as *a line
35//! ending with the group's tag*, never by numbering or prose. Definition
36//! IDs never change (every input's name field is `command.inputs.name`); a
37//! submitted instance is addressed by its **occurrence path**, which
38//! inserts a zero-based index per enclosing repeatable-group occurrence
39//! (`command.inputs[1].name`). [`Answers`] and [`RawAnswers`] are keyed by
40//! occurrence path and expose [`occurrence_count`](Answers::occurrence_count);
41//! indexes belong to an answer instance, not the definition, so they never
42//! enter the fingerprint.
43//!
44//! # Decoding
45//!
46//! One blank rule applies everywhere: a blank answer resolves to the
47//! declared default first; without one, a blank optional field is an
48//! omission and a blank required field is a missing-value error. A
49//! conditional field ([`ScalarField::active_when`]) is asked and enforced
50//! only while its controller holds the expected value; a populated
51//! *inactive* field is an error rather than silently discarded. A default
52//! may instead be dynamic ([`ScalarField::with_dynamic_default`]): a
53//! closure over earlier decoded answers, paired with a mandatory revision
54//! that enters the fingerprint in the static default's place (closures
55//! can't be hashed). Interactive collection re-prompts only on a failed
56//! *entered* answer, keeping earlier answers; batch collection (file/stdin)
57//! accumulates every diagnostic from one pass instead.
58//!
59//! # Compatibility
60//!
61//! The preamble pins a format version, questionnaire ID, and a semantic
62//! fingerprint; parsing requires an exact match on all three; a stale sheet
63//! gets a diagnostic asking for a fresh one rather than a guessed mapping.
64//! The fingerprint covers every property that changes accepted answers —
65//! IDs, kinds, optionality, defaults, constraints, conditions, and
66//! validator/dynamic-default revisions — and ignores wording, numbering,
67//! and ordering. It is a compatibility checksum only, not an
68//! authentication or tamper-detection mechanism.
69//!
70//! # Sensitive content
71//!
72//! Answer sheets are plain text and may hold private or sensitive values:
73//! keep saved sheets out of version control and delete them when done.
74//! Diagnostics identify fields by ID and line number without echoing
75//! answer values; application validators and form messages should do the
76//! same.
77
78mod collect;
79mod decode;
80mod definition;
81mod derive;
82mod fingerprint;
83mod parse;
84mod render;
85
86pub use decode::{AnswerValue, Answers, EarlierAnswers, FormError, ValidationDiagnostic};
87pub use definition::{
88 Condition, Constraint, DynamicDefault, FieldValidator, Group, Item, Questionnaire,
89 QuestionnaireError, Repeat, ScalarField, ScalarKind,
90};
91pub use derive::{
92 QuestionnaireChoiceParseError, QuestionnaireChoices, QuestionnaireInput,
93 QuestionnaireInputError,
94};
95pub use parse::{AnswerSheetDiagnostic, RawAnswers};