pub struct Questionnaire { /* private fields */ }Expand description
An application-owned questionnaire definition.
See the module documentation for the ownership boundary, the rendered answer-sheet format, and the collection and decoding model.
Implementations§
Source§impl Questionnaire
impl Questionnaire
Sourcepub fn read_answer_sheet_file(
&self,
path: impl AsRef<Path>,
) -> Result<RawAnswers, Vec<AnswerSheetDiagnostic>>
pub fn read_answer_sheet_file( &self, path: impl AsRef<Path>, ) -> Result<RawAnswers, Vec<AnswerSheetDiagnostic>>
Read one complete answer sheet from a named file.
The whole document is read and parsed in one pass; the result is the
same RawAnswers representation every collection path produces.
Validate it with decode_answers (or
decode_answers_with).
§Errors
AnswerSheetDiagnostic::UnreadableDocument when the file cannot be
read, otherwise the parser’s accumulated diagnostics.
Sourcepub fn read_answer_sheet_stdin_with(
&self,
reader: &dyn StdinReader,
) -> Result<RawAnswers, Vec<AnswerSheetDiagnostic>>
pub fn read_answer_sheet_stdin_with( &self, reader: &dyn StdinReader, ) -> Result<RawAnswers, Vec<AnswerSheetDiagnostic>>
Read one complete answer sheet from explicitly requested stdin
(e.g. an --answers - style flag), against an explicit
StdinReader — DefaultStdin for the
process’s real stdin (which honors a test override installed via
set_default_stdin_reader),
or an injected reader in tests.
Selecting stdin is an explicit caller decision — this adapter never merges stdin answers with any other source.
§Errors
AnswerSheetDiagnostic::UnreadableDocument when stdin is an
interactive terminal (there is no piped document to read) or fails to
read, otherwise the parser’s accumulated diagnostics.
Sourcepub fn collect_interactive(&self) -> Result<RawAnswers, InputError>
pub fn collect_interactive(&self) -> Result<RawAnswers, InputError>
Collect answers interactively, one prompt per applicable field occurrence.
Prompts through TextPromptSource on the real terminal — and
therefore through any installed
PromptResponder, which is how tests drive
this without a TTY. Every entered answer runs through the same field
decoders and validators as file and stdin answers; a failure on an
entered answer is a local, retryable error — the question
re-prompts with the diagnostic and all previously accepted answers
are kept. A blank entry follows the shared blank rule (default —
static or computed — first, then omission, then a required-answer
re-prompt), a field with a
DynamicDefault shows its computed default
in the prompt message, and inactive conditional fields are skipped
without prompting.
Groups walk their children in place. A repeatable group collects its declared minimum number of occurrences, then asks a yes/no “add another?” question (blank means no) before each further occurrence, stopping unprompted at the declared maximum — so an interactive submission always satisfies the declared bounds, exactly like a well-formed answer sheet.
The result is the same RawAnswers representation the document
adapters produce (each entry already field-valid, keyed by occurrence
path); run decode_answers or
decode_answers_with on it for typed
values and whole-form rules.
§Errors
InputError::PromptCancelledwhen the user cancels (EOF/Ctrl+D).InputError::NoInputwhen stdin is not a terminal and no responder is installed (interactive collection needs one or the other; it never silently reads a piped document), or when a non-input outcome (a responderSkip, or mid-collection terminal loss) lands on a required field without a default — the pass terminates rather than re-prompting a source that produced no input.- Any terminal I/O failure from the underlying prompt source.
Sourcepub fn collect_interactive_with_terminal<T: TerminalIO + 'static>(
&self,
terminal: Arc<T>,
) -> Result<RawAnswers, InputError>
pub fn collect_interactive_with_terminal<T: TerminalIO + 'static>( &self, terminal: Arc<T>, ) -> Result<RawAnswers, InputError>
collect_interactive against an
explicit shared terminal, for callers and tests that inject their own
TerminalIO (e.g. MockTerminal).
Source§impl Questionnaire
impl Questionnaire
Sourcepub fn decode_answers(
&self,
raw: &RawAnswers,
) -> Result<Answers, Vec<ValidationDiagnostic>>
pub fn decode_answers( &self, raw: &RawAnswers, ) -> Result<Answers, Vec<ValidationDiagnostic>>
Decode and validate a complete raw submission.
The definition tree is walked in declaration order (controllers precede their dependents by construction), visiting every submitted occurrence of each repeatable group. For each field occurrence: applicability is evaluated from earlier decoded values in the same scope chain; an active field decodes via the shared field pipeline — default resolution, kind conversion, constraints, application validator; an inactive field must be blank or hold its untouched pre-filled default, otherwise it is reported as populated-but-inapplicable. Repeatable-group occurrence counts outside the declared bounds are reported as structural diagnostics while the occurrences that do exist still decode.
All independent diagnostics accumulate: one pass reports every missing value, conversion failure, constraint violation, field- validation failure, populated inactive field, and occurrence-bound violation together. A field whose controller errored is skipped without piling on speculative diagnostics.
§Errors
The accumulated ValidationDiagnostic list, identifying fields by
stable occurrence path without echoing submitted values.
Sourcepub fn decode_answers_with<F>(
&self,
raw: &RawAnswers,
form: F,
) -> Result<Answers, Vec<ValidationDiagnostic>>
pub fn decode_answers_with<F>( &self, raw: &RawAnswers, form: F, ) -> Result<Answers, Vec<ValidationDiagnostic>>
Decode and validate a complete raw submission, then run the application’s whole-form rules over the successful result.
Field-level diagnostics behave exactly as in
decode_answers. When the field stage
succeeds, form runs once over the decoded Answers and every
returned FormError accumulates as a
ValidationDiagnostic::Form — so a batch submission reports all of
its independent form-level findings together, in the same list and
format as field-level ones. Whole-form rules do not run over a
submission with field-level failures: they would be judging values
that do not exist.
§Errors
The accumulated ValidationDiagnostic list from whichever stages
could run.
Source§impl Questionnaire
impl Questionnaire
Sourcepub fn new(
id: impl Into<String>,
items: Vec<impl Into<Item>>,
) -> Result<Self, QuestionnaireError>
pub fn new( id: impl Into<String>, items: Vec<impl Into<Item>>, ) -> Result<Self, QuestionnaireError>
Create a validated questionnaire definition.
id is the stable questionnaire identity written into every rendered
sheet’s preamble. items — scalar fields and (possibly repeatable)
groups; a Vec<ScalarField> works directly for a flat questionnaire
— are rendered, collected, and decoded in the given order, but order
is cosmetic for identity: reordering items does not change the
fingerprint. The one ordering rule is
structural: a conditional field’s controller must be declared before
it, in the same group or an enclosing one.
Condition expected values are canonicalized here (a bool controller’s
"yes" becomes "true"), so equivalent declarations fingerprint
identically.
§Errors
Returns a QuestionnaireError for an invalid questionnaire or item
ID, a duplicate ID, an empty item list, an empty group, invalid
repeat bounds, a child ID that does not extend its group’s ID, a
field declaring both a static and a dynamic default, or an invalid
default, constraint, condition, validator revision, or
dynamic-default revision.
Sourcepub fn fingerprint(&self) -> &str
pub fn fingerprint(&self) -> &str
The semantic fingerprint (sha256:<hex>).
The fingerprint is a compatibility checksum over the semantic definition — questionnaire ID and each field’s stable ID, kind, optionality, default, constraint, condition, and validator revision. It deliberately excludes wording, presentation order, and everything else cosmetic, so copy-editing a questionnaire never invalidates existing answer sheets, while any change to accepted answers reliably does. It is not an authenticity or tamper-proofing mechanism.
Source§impl Questionnaire
impl Questionnaire
Sourcepub fn parse_answer_sheet(
&self,
text: &str,
) -> Result<RawAnswers, Vec<AnswerSheetDiagnostic>>
pub fn parse_answer_sheet( &self, text: &str, ) -> Result<RawAnswers, Vec<AnswerSheetDiagnostic>>
Parse an edited answer sheet back into RawAnswers.
The document must have been rendered by
render_answer_sheet for this exact
definition: the preamble’s answer-format version, questionnaire ID,
and fingerprint are checked exactly, and any mismatch returns
diagnostics asking for a fresh sheet without reading the body.
The body parses in one linear pass under a single recognition rule:
a line is a question line if and only if it ends with a <id:...>
tag as its last non-whitespace content; any trailing non-blank
character demotes the line to prose. A field’s answer is everything
between its question line and the next question line (or end of
file), outer whitespace trimmed, internal line breaks preserved. A
group tag line opens one group occurrence — repeated items come from
repeated tag lines, so copying a complete rendered group block
submits one more occurrence, whatever its display numbers say.
Bracketed prose, -> bullets, and mid-line tag mentions inside an
answer are inert answer text (a mid-line <id: raises a
warning). The accepted trade-off: an answer
line that itself ends with a schema-valid tag is read as a
question line; there is no escaping mechanism.
§Errors
Returns every accumulated AnswerSheetDiagnostic: compatibility
mismatches, malformed preambles, and unknown, duplicate, or
misplaced tags on question lines. Occurrence counts below a
repeatable group’s minimum (or above its maximum) are not parse
errors — they are structural validation, reported with the other
value diagnostics by decode_answers.
Source§impl Questionnaire
impl Questionnaire
Sourcepub fn render_answer_sheet(&self) -> String
pub fn render_answer_sheet(&self) -> String
Render a blank answer sheet for this questionnaire.
The output is deterministic: rendering the same definition always
produces the same document, including the fingerprint in the preamble.
Each field renders as one question line — cosmetic display number and
wording, a parenthesized type hint, and the line-terminal
<id:...> tag — with the answer expected on the following lines. A
field with a declared static default renders the default pre-filled
as its answer text; every other field — including one with a
dynamic default, whose value depends on
other answers a static sheet cannot see — leaves the answer area
blank. A group
renders its heading line (ending with the group’s tag) and its
children with nested cosmetic numbering; a repeatable group renders
exactly its declared minimum number of occurrence blocks and concise
guidance to copy a complete block when adding an item.
Trait Implementations§
Source§impl Clone for Questionnaire
impl Clone for Questionnaire
Source§fn clone(&self) -> Questionnaire
fn clone(&self) -> Questionnaire
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for Questionnaire
impl Debug for Questionnaire
impl Eq for Questionnaire
Source§impl PartialEq for Questionnaire
impl PartialEq for Questionnaire
impl StructuralPartialEq for Questionnaire
Auto Trait Implementations§
impl !RefUnwindSafe for Questionnaire
impl !UnwindSafe for Questionnaire
impl Freeze for Questionnaire
impl Send for Questionnaire
impl Sync for Questionnaire
impl Unpin for Questionnaire
impl UnsafeUnpin for Questionnaire
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more