Skip to main content

qa_spec/spec/
question.rs

1use crate::expr::Expr;
2use crate::i18n::I18nText;
3use crate::store::StoreTarget;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7/// Supported question data types.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
9#[serde(rename_all = "snake_case")]
10pub enum QuestionType {
11    String,
12    Boolean,
13    Integer,
14    Number,
15    Enum,
16    List,
17}
18
19/// Constraints that can be enforced per question.
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
21pub struct Constraint {
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub pattern: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub min: Option<f64>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub max: Option<f64>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub min_len: Option<usize>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub max_len: Option<usize>,
32}
33
34/// Definition of a single question inside a form.
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
36pub struct QuestionSpec {
37    pub id: String,
38    #[serde(rename = "type")]
39    pub kind: QuestionType,
40    pub title: String,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub title_i18n: Option<I18nText>,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub description: Option<String>,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub description_i18n: Option<I18nText>,
47    #[serde(default)]
48    pub required: bool,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub choices: Option<Vec<String>>,
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub default_value: Option<String>,
53    #[serde(default)]
54    pub secret: bool,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub visible_if: Option<Expr>,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub constraint: Option<Constraint>,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub list: Option<ListSpec>,
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub computed: Option<Expr>,
63    #[serde(default)]
64    pub policy: QuestionPolicy,
65    #[serde(default)]
66    pub computed_overridable: bool,
67}
68
69/// Per-question overrides for progress behavior.
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
71pub struct QuestionPolicy {
72    #[serde(default, skip_serializing_if = "Vec::is_empty")]
73    pub skip_if_present_in: Vec<StoreTarget>,
74    #[serde(default)]
75    pub editable_if_from_default: bool,
76}
77
78/// Definition of a repeatable list whose entries reuse question definitions.
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
80pub struct ListSpec {
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub min_items: Option<usize>,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub max_items: Option<usize>,
85    #[serde(default, skip_serializing_if = "Vec::is_empty")]
86    pub fields: Vec<QuestionSpec>,
87}