1pub(crate) mod embedded;
10mod fields;
11mod layouts;
12mod lenses;
13mod options;
14mod relationship;
15mod tree;
16mod validation;
17
18use std::collections::{HashMap, HashSet};
19
20pub use embedded::EmbeddedForm;
21pub(crate) use fields::option_view;
22pub use fields::{
23 ChoiceField, Control, ControlInput, CustomField, Field, FileField, IntoOptions, TextField,
24 Toggle,
25};
26pub use layouts::{Grid, Group, Repeater, Section};
27pub(crate) use lenses::{ResolvedLens, declare_with, lens_field, lens_field_unique, schema_of};
28pub use lenses::{declare, form_key};
29pub use options::Options;
30pub(crate) use relationship::OptionLoadError;
31pub use relationship::{MAX_RELATIONSHIP_OPTIONS, OptionSource};
32use topcoat::{Result, context::Cx, view::*};
33pub use tree::{IntoSchema, Source};
34pub(crate) use tree::{LeafPlace, Node, render_nodes, walk_absent_groups};
35pub use validation::TypedValue;
36pub(crate) use validation::required_error;
37
38use crate::form::FieldErrors;
39
40#[derive(Debug, Clone)]
42pub(crate) struct ControlCheck {
43 pub(crate) name: String,
45 pub(crate) required: bool,
47 pub(crate) required_error: Option<String>,
49 pub(crate) in_repeater: bool,
51 pub(crate) place: LeafPlace,
53}
54
55impl ControlCheck {
56 pub(crate) fn needs_answer(&self) -> bool {
58 match self.place {
59 LeafPlace::Rendered => true,
60 LeafPlace::Payload => self.in_repeater,
61 LeafPlace::Discriminant => false,
62 }
63 }
64}
65
66#[derive(Debug, Default)]
68pub struct Schema {
69 pub(crate) nodes: Vec<Node>,
70 pub(crate) fields: Vec<Field>,
71}
72
73impl Schema {
74 pub fn is_empty(&self) -> bool {
76 self.nodes.is_empty()
77 }
78
79 pub fn new(children: impl IntoSchema) -> Self {
82 children.into_schema()
83 }
84
85 pub fn empty() -> Self {
86 Self::default()
87 }
88
89 pub fn fields(&self) -> impl Iterator<Item = &Field> {
91 self.fields.iter()
92 }
93
94 pub async fn render<'a>(&self, cx: &'a Cx, source: Source<'_>) -> Result<BoxView<'a>> {
100 let errors = self.declaration_errors();
101 if !errors.is_empty() {
102 return Err(crate::error::misdeclared(&errors));
103 }
104 render_nodes(cx, &self.nodes, &self.fields, &source).await
105 }
106
107 pub(crate) fn append(&mut self, other: Schema) {
109 let Schema { mut nodes, fields } = other;
110 let offset = self.fields.len();
111 for node in &mut nodes {
112 node.offset(offset);
113 }
114 self.fields.extend(fields);
115 self.nodes.extend(nodes);
116 }
117
118 pub(crate) fn embedded_root(&self) -> &embedded::Embedded {
120 match self.nodes.as_slice() {
121 [Node::Embedded(node)] => node,
122 _ => panic!("an embedded value's schema is its one embedded node"),
123 }
124 }
125
126 pub fn normalize_values(&self, values: &mut HashMap<String, String>) {
129 for field in &self.fields {
130 let Some(submitted) = values.get_mut(field.name()) else {
131 continue;
132 };
133 if submitted.trim().is_empty() {
134 continue;
135 }
136 if let Ok(normalized) = field.normalize(submitted) {
137 *submitted = normalized;
138 }
139 }
140 }
141
142 pub fn extend(mut self, other: Schema) -> Schema {
144 self.append(other);
145 self
146 }
147
148 pub(crate) fn controls(&self) -> Vec<ControlCheck> {
150 fn mark(nodes: &[Node], inside: bool, in_repeater: &mut [bool], place: &mut [LeafPlace]) {
151 for node in nodes {
152 match node {
153 Node::Repeater(r) => mark(&r.children.nodes, true, in_repeater, place),
154 Node::Field(_) | Node::Embedded(_) => {
155 node.visit_fields(&mut |index, leaf_place| {
156 in_repeater[index] = inside;
157 place[index] = leaf_place;
158 });
159 }
160 _ => mark(
161 node.children().unwrap_or_default(),
162 inside,
163 in_repeater,
164 place,
165 ),
166 }
167 }
168 }
169 let mut in_repeater = vec![false; self.fields.len()];
170 let mut place = vec![LeafPlace::Rendered; self.fields.len()];
171 mark(&self.nodes, false, &mut in_repeater, &mut place);
172 self.fields
173 .iter()
174 .zip(in_repeater)
175 .zip(place)
176 .map(|((field, in_repeater), place)| {
177 let errors = field.validate("");
178 ControlCheck {
179 name: field.name().to_string(),
180 required: !errors.is_empty(),
181 required_error: errors
182 .into_iter()
183 .next()
184 .map(|error| error.message)
185 .or_else(|| Some(required_error(field.label_str()))),
186 in_repeater,
187 place,
188 }
189 })
190 .collect()
191 }
192
193 pub fn unknown_keys(&self, values: &HashMap<String, String>) -> Vec<String> {
196 let known: HashSet<&str> = self.fields.iter().map(Field::name).collect();
197 let mut out: Vec<String> = values
198 .keys()
199 .filter(|k| !known.contains(k.as_str()))
200 .cloned()
201 .collect();
202 out.sort();
203 out
204 }
205
206 pub fn declaration_errors(&self) -> Vec<crate::DeclarationErrorKind> {
209 let mut errors = Vec::new();
210 let mut seen = HashSet::new();
211 for field in &self.fields {
212 match field.misdeclared() {
213 Some(error) => errors.push(error.clone()),
214 None if !seen.insert(field.name()) => {
215 errors.push(crate::DeclarationErrorKind::DuplicateField {
216 name: field.name().to_string(),
217 });
218 }
219 None => {}
220 }
221 }
222 errors
223 }
224
225 pub fn validate(&self, values: &HashMap<String, String>) -> FieldErrors {
228 let mut errors = FieldErrors::new();
229 let mut skip: HashSet<String> = HashSet::new();
230 walk_absent_groups(
231 &self.nodes,
232 &self.fields,
233 values,
234 &mut skip,
235 &mut errors,
236 false,
237 );
238 for field in &self.fields {
239 if skip.contains(field.name()) {
240 continue;
241 }
242 let value = values.get(field.name()).map(String::as_str).unwrap_or("");
243 for error in field.validate(value) {
244 errors.push(error);
245 }
246 }
247 errors
248 }
249
250 pub(crate) fn absent_fields(&self, values: &HashMap<String, String>) -> HashSet<String> {
252 let mut skip = HashSet::new();
253 let mut discarded = FieldErrors::new();
254 walk_absent_groups(
255 &self.nodes,
256 &self.fields,
257 values,
258 &mut skip,
259 &mut discarded,
260 false,
261 );
262 skip
263 }
264
265 pub(crate) fn renders_error_key(&self, values: &HashMap<String, String>, key: &str) -> bool {
268 fn labels(nodes: &[Node], key: &str) -> bool {
269 nodes.iter().any(|node| match node {
270 Node::Repeater(repeater) => {
271 repeater.label == key || labels(&repeater.children.nodes, key)
272 }
273 node => node
274 .children()
275 .is_some_and(|children| labels(children, key)),
276 })
277 }
278 let hidden = self.hidden_fields(values);
279 let renders_field = self
280 .fields
281 .iter()
282 .any(|field| field.name() == key && !hidden.contains(key));
283 renders_field || labels(&self.nodes, key)
284 }
285
286 fn hidden_fields(&self, values: &HashMap<String, String>) -> HashSet<String> {
288 fn walk(nodes: &[Node], values: &HashMap<String, String>, out: &mut Vec<usize>) {
289 for node in nodes {
290 match node {
291 Node::Embedded(embedded) => embedded.hidden_fields(values, out),
292 node => {
293 if let Some(children) = node.children() {
294 walk(children, values, out);
295 }
296 }
297 }
298 }
299 }
300 let mut indices = Vec::new();
301 walk(&self.nodes, values, &mut indices);
302 indices
303 .into_iter()
304 .map(|index| self.fields[index].name().to_string())
305 .collect()
306 }
307
308 pub async fn validate_async(&self, cx: &Cx, values: &HashMap<String, String>) -> FieldErrors {
310 let mut errors = self.validate(values);
311 let absent = self.absent_fields(values);
312 for field in &self.fields {
313 let name = field.name();
314 if errors.contains_key(name) || absent.contains(name) {
315 continue;
316 }
317 let Some(value) = values.get(name) else {
318 continue;
319 };
320 for message in field.validate_exists(cx, value).await {
321 errors.add(name, message);
322 }
323 }
324 errors
325 }
326
327 pub(crate) async fn recheck_relationships(
329 &self,
330 cx: &Cx,
331 values: &HashMap<String, String>,
332 ex: &mut dyn toasty::Executor,
333 ) -> FieldErrors {
334 let mut errors = FieldErrors::new();
335 let absent = self.absent_fields(values);
336 for field in &self.fields {
337 let name = field.name();
338 if absent.contains(name) {
339 continue;
340 }
341 let Some(value) = values.get(name) else {
342 continue;
343 };
344 for message in field.recheck(cx, value, &mut *ex).await {
345 errors.add(name, message);
346 }
347 }
348 errors
349 }
350}
351
352#[cfg(test)]
353mod tests;