Skip to main content

Questionnaire

Struct Questionnaire 

Source
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

Source

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.

Source

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 StdinReaderDefaultStdin 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.

Source

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::PromptCancelled when the user cancels (EOF/Ctrl+D).
  • InputError::NoInput when 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 responder Skip, 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.
Source

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

Source

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.

Source

pub fn decode_answers_with<F>( &self, raw: &RawAnswers, form: F, ) -> Result<Answers, Vec<ValidationDiagnostic>>
where F: FnOnce(&Answers) -> Vec<FormError>,

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

Source

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.

Source

pub fn id(&self) -> &str

The stable questionnaire ID.

Source

pub fn items(&self) -> &[Item]

The declared items, in presentation order.

Source

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

Source

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

Source

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

Source§

fn clone(&self) -> Questionnaire

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Questionnaire

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Questionnaire

Source§

impl PartialEq for Questionnaire

Source§

fn eq(&self, other: &Questionnaire) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Questionnaire

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.