1use std::collections::BTreeMap;
7
8use serde::Serialize;
9
10use crate::{
11 semantic_type_text, ApplicationSemanticModel, FormControlChannel, SemanticId, SerializableValue,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq, Default)]
15pub struct FormInspectionRegistry {
16 pub records: BTreeMap<SemanticId, FormInspection>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
20pub struct FormInspection {
21 pub role: &'static str,
22 pub form: String,
23 pub owner_component: String,
24 pub field_order: Vec<String>,
25 pub field_semantic_type: Option<String>,
26 pub initial_value: Option<SerializableValue>,
27 pub bindings: Vec<String>,
28 pub binding_channels: Vec<String>,
29 pub validation_rules: Vec<String>,
30 pub source_rules: Vec<String>,
31 pub dependent_rules: Vec<String>,
32 pub dirty_tracking: Option<String>,
33 pub touched_tracking: Option<String>,
34 pub submission_plan: Option<String>,
35 pub serialization_plan: Option<String>,
36 pub reset_plan: Option<String>,
37 pub instances: Vec<FormInspectionInstance>,
38 pub runtime_registry_member: bool,
39 pub runtime_artifact_member: bool,
40 pub resume_planned: bool,
41 pub blocked_reasons: Vec<String>,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
45pub struct FormInspectionInstance {
46 pub form_instance: String,
47 pub component_instance: String,
48 pub value_slots: Vec<String>,
49 pub dirty_slots: Vec<String>,
50 pub touched_slots: Vec<String>,
51 pub validation_slots: Vec<String>,
52 pub aggregate_slot: String,
53 pub submission_slot: String,
54 pub input_program_fields: Vec<String>,
55 pub blur_program_fields: Vec<String>,
56}
57
58#[must_use]
65#[allow(clippy::too_many_lines)]
66pub fn build_form_inspection_registry(model: &ApplicationSemanticModel) -> FormInspectionRegistry {
67 let artifact = crate::build_runtime_forms_artifact(model);
68 let resume = crate::build_resume_plan(model);
69 let mut records = BTreeMap::new();
70 for form in model.forms.values() {
71 let fields = model
72 .form_fields
73 .values()
74 .filter(|field| field.owner_form == form.id)
75 .collect::<Vec<_>>();
76 let instances = model
77 .optimized_form_ir
78 .optimized
79 .instances
80 .values()
81 .filter(|instance| instance.form == form.id)
82 .map(|instance| FormInspectionInstance {
83 form_instance: instance.id.to_string(),
84 component_instance: instance.component_instance.to_string(),
85 value_slots: instance
86 .storage
87 .value
88 .values()
89 .map(|id| id.as_str().to_string())
90 .collect(),
91 dirty_slots: instance
92 .storage
93 .dirty
94 .values()
95 .map(|id| id.as_str().to_string())
96 .collect(),
97 touched_slots: instance
98 .storage
99 .touched
100 .values()
101 .map(|id| id.as_str().to_string())
102 .collect(),
103 validation_slots: instance
104 .storage
105 .validation
106 .values()
107 .map(|id| id.as_str().to_string())
108 .collect(),
109 aggregate_slot: instance.storage.aggregate.as_str().to_string(),
110 submission_slot: instance.storage.submission.as_str().to_string(),
111 input_program_fields: instance.input.keys().map(ToString::to_string).collect(),
112 blur_program_fields: instance.blur.keys().map(ToString::to_string).collect(),
113 })
114 .collect::<Vec<_>>();
115 let base = FormInspection {
116 role: "form",
117 form: form.id.to_string(),
118 owner_component: form.owner.entity_id().expect("Form owner").to_string(),
119 field_order: fields.iter().map(|field| field.id.to_string()).collect(),
120 field_semantic_type: None,
121 initial_value: None,
122 bindings: model
123 .form_field_bindings
124 .values()
125 .filter(|binding| binding.form == form.id)
126 .map(|binding| binding.id.to_string())
127 .collect(),
128 binding_channels: model
129 .form_field_bindings
130 .values()
131 .filter(|binding| binding.form == form.id)
132 .map(|binding| form_control_channel_text(binding.channel).to_string())
133 .collect(),
134 validation_rules: model
135 .validation_rules
136 .values()
137 .filter(|rule| rule.owner_form == form.id)
138 .map(|rule| rule.id.to_string())
139 .collect(),
140 source_rules: Vec::new(),
141 dependent_rules: Vec::new(),
142 dirty_tracking: model
143 .form_tracking
144 .dirty
145 .plan(&form.id)
146 .map(|plan| plan.id.as_str().to_string()),
147 touched_tracking: model
148 .form_tracking
149 .touched
150 .plan(&form.id)
151 .map(|plan| plan.id.as_str().to_string()),
152 submission_plan: model
153 .submissions
154 .plan(&form.id)
155 .map(|plan| plan.id.as_str().to_string()),
156 serialization_plan: model
157 .serialization
158 .plans
159 .get(&crate::SerializationPlanId::for_form(&form.id))
160 .map(|plan| plan.id.as_str().to_string()),
161 reset_plan: model
162 .reset
163 .plans
164 .get(&crate::ResetPlanId::for_form(&form.id))
165 .map(|plan| plan.id.as_str().to_string()),
166 instances,
167 runtime_registry_member: model.runtime_forms.forms.contains_key(&form.id),
168 runtime_artifact_member: artifact
169 .forms
170 .iter()
171 .any(|record| record.id == form.id.to_string()),
172 resume_planned: resume
173 .form_instances
174 .iter()
175 .any(|record| record.form == form.id.to_string()),
176 blocked_reasons: Vec::new(),
177 };
178 records.insert(form.id.as_semantic_id().clone(), base.clone());
179 for field in fields {
180 let mut record = base.clone();
181 record.role = "field";
182 record.field_order = vec![field.id.to_string()];
183 record.field_semantic_type = Some(semantic_type_text(&field.semantic_type));
184 record.initial_value = Some(field.initial_value.clone());
185 record.bindings = model
186 .form_field_bindings
187 .values()
188 .filter(|binding| binding.field == field.id)
189 .map(|binding| binding.id.to_string())
190 .collect();
191 record.binding_channels = model
192 .form_field_bindings
193 .values()
194 .filter(|binding| binding.field == field.id)
195 .map(|binding| form_control_channel_text(binding.channel).to_string())
196 .collect();
197 record.validation_rules = model
198 .validation_rules
199 .values()
200 .filter(|rule| rule.target_field == field.id)
201 .map(|rule| rule.id.to_string())
202 .collect();
203 record.source_rules = model
204 .validation_dependency_plans
205 .dependencies
206 .values()
207 .filter(|dependency| dependency.source_field == field.id)
208 .map(|dependency| dependency.dependent_rule.to_string())
209 .collect();
210 record.dependent_rules = model
211 .validation_dependency_plans
212 .dependencies
213 .values()
214 .filter(|dependency| dependency.target_field == field.id)
215 .map(|dependency| dependency.dependent_rule.to_string())
216 .collect();
217 record.dirty_tracking = model
218 .form_tracking
219 .dirty
220 .tracking(&field.id)
221 .map(|tracking| tracking.id.as_str().to_string());
222 record.touched_tracking = model
223 .form_tracking
224 .touched
225 .tracking(&field.id)
226 .map(|tracking| tracking.id.as_str().to_string());
227 records.insert(field.id.as_semantic_id().clone(), record);
228 }
229 }
230 for binding in model.form_field_bindings.values() {
231 let mut record = records
232 .get(binding.form.as_semantic_id())
233 .expect("binding Form inspection")
234 .clone();
235 record.role = "binding";
236 record.bindings = vec![binding.id.to_string()];
237 record.binding_channels = vec![form_control_channel_text(binding.channel).to_string()];
238 records.insert(binding.id.as_semantic_id().clone(), record);
239 }
240 for rule in model.validation_rules.values() {
241 let mut record = records
242 .get(rule.owner_form.as_semantic_id())
243 .expect("rule Form inspection")
244 .clone();
245 record.role = "validation-rule";
246 record.validation_rules = vec![rule.id.to_string()];
247 records.insert(rule.id.as_semantic_id().clone(), record);
248 }
249 FormInspectionRegistry { records }
250}
251
252const fn form_control_channel_text(channel: FormControlChannel) -> &'static str {
253 match channel {
254 FormControlChannel::Value => "value",
255 FormControlChannel::NumericValue => "numeric-value",
256 FormControlChannel::Checked => "checked",
257 FormControlChannel::RadioValue => "radio-value",
258 FormControlChannel::SelectedValue => "selected-value",
259 FormControlChannel::SelectedValues => "selected-values",
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 #[test]
266 fn projects_canonical_form_field_binding_and_rule_records() {
267 let model = crate::build_application_semantic_model(&presolve_parser::parse_file(
268 "src/X.tsx",
269 r#"@component("x") class X { @form() @serialize("json") form!: Form; @field(this.form) value = ""; @action() @submit(this.form) save(): void {} render() { return <form form={this.form}><input field={this.value}/></form>; } }"#,
270 ));
271 let registry = super::build_form_inspection_registry(&model);
272 assert_eq!(registry.records.len(), 3);
273 assert!(registry
274 .records
275 .values()
276 .any(|record| record.role == "form" && record.runtime_artifact_member));
277 }
278}