1mod condition;
7pub(crate) mod embedded;
8pub(crate) mod fields;
9mod layouts;
10mod lenses;
11mod options;
12pub(crate) mod relationship;
13pub(crate) mod repeater;
14pub(crate) mod tree;
15pub(crate) mod validation;
16
17use std::{
18 collections::{HashMap, HashSet},
19 marker::PhantomData,
20};
21
22pub use condition::Watched;
23pub use embedded::EmbeddedForm;
24use fields::ChoiceControl;
25pub use fields::{
26 ChoiceField, CustomField, Field, FileField, IntoOptions, RepeaterField, TextField, Toggle,
27};
28pub(crate) use fields::{option_view, read_only, stored_upload, value_cell};
29pub use layouts::{Grid, Group, Section};
30pub(crate) use lenses::Binding;
31pub use lenses::{FieldResolver, form_key};
32pub use options::Options;
33pub use relationship::MAX_RELATIONSHIP_OPTIONS;
34pub(crate) use relationship::{OptionLoadError, OptionSource};
35pub use repeater::{MAX_ROWS, RepeaterItem, parse_items, write_items};
36use topcoat::{Result, context::Cx, view::*};
37pub use tree::{IntoSchema, Source};
38pub(crate) use tree::{Node, Retype, render_nodes};
39use tree::{bind_nodes, unbound_values};
40pub(crate) use validation::TypedValue;
41
42use crate::form::FieldErrors;
43
44pub struct Schema<F = ()> {
65 pub(crate) nodes: Vec<Node>,
66 pub(crate) fields: Vec<Field>,
67 form: PhantomData<fn() -> F>,
68}
69
70impl<F> Default for Schema<F> {
71 fn default() -> Self {
72 Self::from_parts(Vec::new(), Vec::new())
73 }
74}
75
76impl<F> std::fmt::Debug for Schema<F> {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.debug_struct("Schema")
79 .field("nodes", &self.nodes)
80 .field("fields", &self.fields)
81 .finish()
82 }
83}
84
85impl Schema {
86 pub fn empty() -> Self {
89 Self::default()
90 }
91}
92
93impl<F> Retype for Schema<F> {
94 type As<G> = Schema<G>;
95
96 fn retype<G>(self) -> Schema<G> {
97 Schema::from_parts(self.nodes, self.fields)
98 }
99}
100
101impl<F> Schema<F> {
102 pub(crate) fn from_parts(nodes: Vec<Node>, fields: Vec<Field>) -> Self {
103 Self {
104 nodes,
105 fields,
106 form: PhantomData,
107 }
108 }
109
110 pub fn is_empty(&self) -> bool {
112 self.nodes.is_empty()
113 }
114
115 pub fn new(children: impl IntoSchema<F>) -> Self {
118 children.into_schema()
119 }
120
121 pub fn fields(&self) -> impl Iterator<Item = &Field> {
124 self.fields.iter()
125 }
126
127 pub fn bind(mut self, db: &toasty::Db) -> Self {
132 self.bind_with(&FieldResolver::of_db(db));
133 self
134 }
135
136 pub(crate) fn bind_with(&mut self, resolver: &FieldResolver) {
139 bind_nodes(&mut self.nodes, &mut self.fields, resolver);
140 for field in &self.fields {
141 field.bind(resolver);
142 }
143 }
144
145 pub(crate) fn require(&mut self, required: &HashSet<&str>) {
147 for field in &mut self.fields {
148 let key = required.contains(field.name());
149 field.set_required(key);
150 }
151 }
152
153 pub async fn render<'a>(&self, cx: &'a Cx, source: Source<'_>) -> Result<BoxView<'a>> {
159 let errors = self.declaration_errors();
160 if !errors.is_empty() {
161 return Err(crate::error::misdeclared(&errors));
162 }
163 let source = source.watching(self.watched());
164 render_nodes(cx, &self.nodes, &self.fields, &source).await
165 }
166
167 pub(crate) fn append(&mut self, other: Schema<F>) {
169 let Schema {
170 mut nodes, fields, ..
171 } = other;
172 let offset = self.fields.len();
173 for node in &mut nodes {
174 node.offset(offset);
175 }
176 self.fields.extend(fields);
177 self.nodes.extend(nodes);
178 }
179
180 pub(crate) fn embedded_root(&self) -> &embedded::Embedded {
182 match self.nodes.as_slice() {
183 [Node::Embedded(node)] => node,
184 _ => panic!("an embedded value's schema is its one embedded node"),
185 }
186 }
187
188 pub fn fold_repeaters(
199 &self,
200 values: &mut HashMap<String, String>,
201 ) -> std::result::Result<(), String> {
202 for field in &self.fields {
203 if let Some(repeater) = field.as_repeater() {
204 repeater.fold(field.name(), values)?;
205 }
206 }
207 Ok(())
208 }
209
210 pub(crate) fn fold_choices(
214 &self,
215 values: &mut HashMap<String, String>,
216 lists: &HashMap<String, Vec<String>>,
217 ) {
218 let multiple = self
219 .fields
220 .iter()
221 .filter(|field| field.as_choice().is_some_and(ChoiceControl::is_multiple));
222 for field in multiple {
223 let Some(posted) = lists.get(field.name()) else {
224 continue;
225 };
226 let mut keys: Vec<String> = Vec::new();
227 for value in posted.iter().map(|value| value.trim()) {
228 if !value.is_empty() && !keys.iter().any(|key| key == value) {
229 keys.push(value.to_string());
230 }
231 }
232 values.insert(field.name().to_string(), crate::form::encode_list(&keys));
233 }
234 }
235
236 pub(crate) fn read_input(
245 &self,
246 values: &HashMap<String, String>,
247 lists: &HashMap<String, Vec<String>>,
248 reserved: &[&str],
249 ) -> Result<HashMap<String, String>> {
250 use topcoat::router::error::bad_request;
251
252 let mut input = values.clone();
253 input.retain(|key, _| !reserved.contains(&key.as_str()));
254 self.fold_repeaters(&mut input).map_err(bad_request)?;
255 self.fold_choices(&mut input, lists);
256 let unknown = self.unknown_keys(&input);
257 if !unknown.is_empty() {
258 return Err(bad_request(format!("unknown field(s): {}", unknown.join(", "))).into());
259 }
260 for key in self.condition_hidden(&input) {
261 input.remove(&key);
262 }
263 Ok(input)
264 }
265
266 pub fn extend(mut self, other: Schema<F>) -> Schema<F> {
268 self.append(other);
269 self
270 }
271
272 pub(crate) fn unknown_keys(&self, values: &HashMap<String, String>) -> Vec<String> {
275 let known: HashSet<&str> = self.fields.iter().map(Field::name).collect();
276 let mut out: Vec<String> = values
277 .keys()
278 .filter(|k| !known.contains(k.as_str()))
279 .cloned()
280 .collect();
281 out.sort();
282 out
283 }
284
285 pub(crate) fn empty_choices(&self) -> Vec<crate::DeclarationErrorKind> {
290 self.fields
291 .iter()
292 .filter(|field| field.offers_nothing())
293 .map(|field| crate::DeclarationErrorKind::EmptyChoice {
294 field: field.name().to_string(),
295 })
296 .collect()
297 }
298
299 fn dependent_errors(&self) -> Vec<crate::DeclarationErrorKind> {
303 let mut errors = Vec::new();
304 for field in &self.fields {
305 let Some(choice) = field.as_choice() else {
306 continue;
307 };
308 if choice.misdeclared_parent() {
309 errors.push(crate::DeclarationErrorKind::MisdeclaredDependentChoice {
310 field: field.name().to_string(),
311 });
312 }
313 let Some(parent) = choice.parent_key() else {
314 continue;
315 };
316 match self.fields.iter().find(|placed| placed.name() == parent) {
317 None => errors.push(crate::DeclarationErrorKind::UnplacedParentField {
318 field: field.name().to_string(),
319 parent: parent.to_string(),
320 }),
321 Some(parent)
322 if choice.is_multiple()
323 || parent.as_choice().is_some_and(ChoiceControl::is_multiple) =>
324 {
325 errors.push(crate::DeclarationErrorKind::MultipleDependentChoice {
326 field: field.name().to_string(),
327 });
328 }
329 Some(_) => {}
330 }
331 }
332 errors
333 }
334
335 pub fn declaration_errors(&self) -> Vec<crate::DeclarationErrorKind> {
338 let mut unbound = Vec::new();
339 unbound_values(&self.nodes, &mut unbound);
340 let mut errors: Vec<_> = unbound
341 .into_iter()
342 .map(|item| crate::DeclarationErrorKind::Unbound { item })
343 .collect();
344 errors.extend(self.condition_errors());
345 errors.extend(self.dependent_errors());
346 let mut seen = HashSet::new();
347 for field in &self.fields {
348 match field.misdeclared() {
349 Some(error) => errors.push(error),
350 None if !seen.insert(field.name()) => {
351 errors.push(crate::DeclarationErrorKind::DuplicateField {
352 name: field.name().to_string(),
353 });
354 }
355 None => {}
356 }
357 }
358 errors
359 }
360
361 pub(crate) fn hidden_fields(&self, values: &HashMap<String, String>) -> HashSet<String> {
364 fn walk(nodes: &[Node], values: &HashMap<String, String>, out: &mut Vec<usize>) {
365 for node in nodes {
366 match node {
367 Node::Embedded(embedded) => embedded.hidden_fields(values, out),
368 node => {
369 if let Some(children) = node.children() {
370 walk(children, values, out);
371 }
372 }
373 }
374 }
375 }
376 let mut indices = Vec::new();
377 walk(&self.nodes, values, &mut indices);
378 indices
379 .into_iter()
380 .map(|index| self.fields[index].name().to_string())
381 .chain(self.condition_hidden(values))
382 .collect()
383 }
384
385 pub(crate) async fn check_controls(
389 &self,
390 cx: &Cx,
391 values: &HashMap<String, String>,
392 stored: &HashMap<String, String>,
393 errors: &mut FieldErrors,
394 ) {
395 let hidden = self.hidden_fields(values);
396 for field in &self.fields {
397 let name = field.name();
398 if errors.contains_key(name) || hidden.contains(name) {
399 continue;
400 }
401 let Some(value) = values.get(name) else {
402 continue;
403 };
404 if let Some(repeater) = field.as_repeater() {
405 repeater.check(cx, name, value, errors).await;
406 continue;
407 }
408 if let Some(error) = field.check(value) {
409 errors.push(error);
410 continue;
411 }
412 let parent = field.parent_key().and_then(|key| values.get(key));
413 for message in field
414 .validate_exists(
415 cx,
416 value,
417 parent.map(String::as_str),
418 stored.get(name).map(String::as_str),
419 )
420 .await
421 {
422 errors.add(name, message);
423 }
424 }
425 }
426
427 #[cfg(test)]
429 pub(crate) async fn checked(&self, cx: &Cx, values: &HashMap<String, String>) -> FieldErrors {
430 let mut errors = FieldErrors::new();
431 self.check_controls(cx, values, &HashMap::new(), &mut errors)
432 .await;
433 errors
434 }
435
436 pub(crate) async fn recheck_relationships(
439 &self,
440 cx: &Cx,
441 values: &HashMap<String, String>,
442 stored: &HashMap<String, String>,
443 ex: &mut dyn toasty::Executor,
444 ) -> FieldErrors {
445 let mut errors = FieldErrors::new();
446 let hidden = self.hidden_fields(values);
447 for field in &self.fields {
448 let name = field.name();
449 if hidden.contains(name) {
450 continue;
451 }
452 let Some(value) = values.get(name) else {
453 continue;
454 };
455 let parent = field.parent_key().and_then(|key| values.get(key));
456 for message in field
457 .recheck(
458 cx,
459 value,
460 parent.map(String::as_str),
461 stored.get(name).map(String::as_str),
462 &mut *ex,
463 )
464 .await
465 {
466 errors.add(name, message);
467 }
468 }
469 errors
470 }
471
472 pub(crate) async fn keep_unseen_links(
476 &self,
477 cx: &Cx,
478 values: &mut HashMap<String, String>,
479 stored: &HashMap<String, String>,
480 ex: &mut dyn toasty::Executor,
481 ) {
482 for field in &self.fields {
483 let name = field.name();
484 let (Some(value), Some(held)) = (values.get(name), stored.get(name)) else {
485 continue;
486 };
487 if let Some(kept) = field.keep_unseen(cx, value, held, &mut *ex).await {
488 values.insert(name.to_string(), kept);
489 }
490 }
491 }
492}
493
494#[cfg(test)]
495mod tests;