Skip to main content

presolve_compiler/
form_serialization.rs

1//! I10 deterministic declaration-level Form serialization planning.
2
3use std::collections::BTreeMap;
4
5use crate::{
6    serialization_compatibility, ComponentNode, FieldId, FormEntity, FormFieldEntity, FormId,
7    FormSubmissionPlan, SerializableValue, SerializationCompatibility, SerializationPlanId,
8    SourceProvenance, SubmissionPlanId,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FormSerializationFormat {
13    Json,
14    FormData,
15    UrlEncoded,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum FormFieldSerializationConversion {
20    JsonValue,
21    FormDataScalar,
22    UrlEncodedScalar,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct SerializedFieldPlan {
27    pub field: FieldId,
28    pub key: String,
29    pub declaration_order: usize,
30    pub conversion: FormFieldSerializationConversion,
31    pub initial_value: SerializableValue,
32    pub provenance: SourceProvenance,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct SerializationDeclarationFact {
37    pub form: Option<FormId>,
38    pub invoked: bool,
39    pub argument_count: usize,
40    pub format: Option<String>,
41    pub provenance: SourceProvenance,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum SerializationPlanStatus {
46    Valid,
47    InvalidDecorator,
48    NonSerializableField,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct FormSerializationPlan {
53    pub id: SerializationPlanId,
54    pub form: FormId,
55    pub format: FormSerializationFormat,
56    pub fields: Vec<SerializedFieldPlan>,
57    pub linked_submission: Option<SubmissionPlanId>,
58    pub status: SerializationPlanStatus,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Default)]
62pub struct SerializationProducts {
63    pub declarations: Vec<SerializationDeclarationFact>,
64    pub plans: BTreeMap<SerializationPlanId, FormSerializationPlan>,
65}
66
67#[must_use]
68pub fn collect_serialization_products(
69    components: &[ComponentNode],
70    forms: &BTreeMap<FormId, FormEntity>,
71    fields: &BTreeMap<FieldId, FormFieldEntity>,
72    submissions: &BTreeMap<SubmissionPlanId, FormSubmissionPlan>,
73) -> SerializationProducts {
74    let declarations = components
75        .iter()
76        .flat_map(|component| component.serialization_declaration_facts.iter())
77        .map(|fact| SerializationDeclarationFact {
78            form: fact.declaration_field.as_ref().and_then(|authored| {
79                forms
80                    .values()
81                    .find(|form| &form.authored_field == authored)
82                    .map(|form| form.id.clone())
83            }),
84            invoked: fact.invoked,
85            argument_count: fact.argument_count,
86            format: fact.format.clone(),
87            provenance: fact.decorator_provenance.clone(),
88        })
89        .collect::<Vec<_>>();
90    let mut plans = BTreeMap::new();
91    for form in forms.values() {
92        let matching = declarations
93            .iter()
94            .filter(|declaration| declaration.form.as_ref() == Some(&form.id))
95            .collect::<Vec<_>>();
96        let decorator_valid = matching.len() <= 1
97            && matching.iter().all(|declaration| {
98                declaration.invoked
99                    && declaration.argument_count == 1
100                    && matches!(
101                        declaration.format.as_deref(),
102                        Some("json" | "form-data" | "url-encoded")
103                    )
104            });
105        let format = matching
106            .first()
107            .and_then(|declaration| declaration.format.as_deref())
108            .map_or(FormSerializationFormat::Json, |format| match format {
109                "form-data" => FormSerializationFormat::FormData,
110                "url-encoded" => FormSerializationFormat::UrlEncoded,
111                _ => FormSerializationFormat::Json,
112            });
113        let conversion = match format {
114            FormSerializationFormat::Json => FormFieldSerializationConversion::JsonValue,
115            FormSerializationFormat::FormData => FormFieldSerializationConversion::FormDataScalar,
116            FormSerializationFormat::UrlEncoded => {
117                FormFieldSerializationConversion::UrlEncodedScalar
118            }
119        };
120        let mut ordered = fields
121            .values()
122            .filter(|field| field.owner_form == form.id)
123            .collect::<Vec<_>>();
124        ordered.sort_by(|left, right| {
125            (left.declaration_order, &left.id).cmp(&(right.declaration_order, &right.id))
126        });
127        let serializable = ordered.iter().all(|field| {
128            serialization_compatibility(&field.semantic_type)
129                == SerializationCompatibility::Serializable
130        });
131        let status = if !decorator_valid {
132            SerializationPlanStatus::InvalidDecorator
133        } else if !serializable {
134            SerializationPlanStatus::NonSerializableField
135        } else {
136            SerializationPlanStatus::Valid
137        };
138        plans.insert(
139            SerializationPlanId::for_form(&form.id),
140            FormSerializationPlan {
141                id: SerializationPlanId::for_form(&form.id),
142                form: form.id.clone(),
143                format,
144                fields: ordered
145                    .into_iter()
146                    .map(|field| SerializedFieldPlan {
147                        field: field.id.clone(),
148                        key: field.path.join("."),
149                        declaration_order: field.declaration_order,
150                        conversion,
151                        initial_value: field.initial_value.clone(),
152                        provenance: field.provenance.clone(),
153                    })
154                    .collect(),
155                linked_submission: submissions
156                    .contains_key(&SubmissionPlanId::for_form(&form.id))
157                    .then(|| SubmissionPlanId::for_form(&form.id)),
158                status,
159            },
160        );
161    }
162    SerializationProducts {
163        declarations,
164        plans,
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use crate::{
171        build_application_semantic_model, FormId, FormSerializationFormat, SerializationPlanStatus,
172    };
173
174    #[test]
175    fn plans_implicit_json_and_explicit_scalar_formats_in_i3_order() {
176        let parsed = presolve_parser::parse_file(
177            "src/Forms.tsx",
178            r#"
179@component("forms") class Forms {
180  @form() implicit!: Form;
181  @form() @serialize("url-encoded") encoded!: Form;
182  @field(this.implicit) object = { city: "Austin" };
183  @field(this.implicit) value = false;
184  @field(this.encoded) name = "Ada";
185  render() { return <input field={this.name} />; }
186}
187"#,
188        );
189        let model = build_application_semantic_model(&parsed);
190        let implicit = FormId::for_owner(&model.components[0].id, "implicit");
191        let encoded = FormId::for_owner(&model.components[0].id, "encoded");
192        let implicit_plan = model
193            .serialization
194            .plans
195            .get(&crate::SerializationPlanId::for_form(&implicit))
196            .expect("implicit plan");
197        let encoded_plan = model
198            .serialization
199            .plans
200            .get(&crate::SerializationPlanId::for_form(&encoded))
201            .expect("encoded plan");
202        assert_eq!(implicit_plan.format, FormSerializationFormat::Json);
203        assert_eq!(
204            implicit_plan
205                .fields
206                .iter()
207                .map(|field| field.key.as_str())
208                .collect::<Vec<_>>(),
209            vec!["object", "value"]
210        );
211        assert_eq!(encoded_plan.format, FormSerializationFormat::UrlEncoded);
212        assert_eq!(encoded_plan.status, SerializationPlanStatus::Valid);
213    }
214
215    #[test]
216    fn retains_invalid_format_as_non_executable_plan_status() {
217        let parsed = presolve_parser::parse_file(
218            "src/Bad.tsx",
219            r#"
220@component("bad") class Bad {
221  @form() @serialize("xml") value!: Form;
222  @field(this.value) name = "";
223  render() { return <input field={this.name} />; }
224}
225"#,
226        );
227        let model = build_application_semantic_model(&parsed);
228        let form = FormId::for_owner(&model.components[0].id, "value");
229        assert_eq!(
230            model.serialization.plans[&crate::SerializationPlanId::for_form(&form)].status,
231            SerializationPlanStatus::InvalidDecorator
232        );
233    }
234}