1use 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}