1mod comparison;
7mod condition;
8pub(crate) mod embedded;
9pub(crate) mod fields;
10pub(crate) mod input;
11mod layouts;
12mod lenses;
13mod mask;
14mod options;
15mod read;
16pub(crate) mod relationship;
17pub(crate) mod repeater;
18pub(crate) mod tree;
19
20use std::{collections::HashSet, marker::PhantomData};
21
22pub use condition::Watched;
23pub use embedded::EmbeddedForm;
24use fields::ChoiceControl;
25#[cfg(feature = "rich-text")]
26pub use fields::RichTextField;
27pub use fields::{
28 ChoiceField, CustomField, Field, FileField, IntoFormValue, IntoOptions, RepeaterField, Switch,
29 TextField, Toggle,
30};
31pub(crate) use fields::{
32 MAX_FORM_BYTES,
33 file::{FileHref, file_href},
34 is_linkable, is_rooted, option_view, read_only, stored_upload, value_cell,
35};
36pub(crate) use input::FormInput;
37pub use layouts::{Grid, Group, HoldsTabs, Section, Tab, Tabs};
38pub(crate) use layouts::{tab_id, tab_view, tabs_chrome};
39pub(crate) use lenses::Binding;
40pub use lenses::form_key;
41pub(crate) use mask::FormMask;
42pub use options::Options;
43pub use relationship::MAX_RELATIONSHIP_OPTIONS;
44pub(crate) use relationship::{
45 OptionLoadError, OptionSource, clamp_query_term, ensure_option_access,
46};
47pub use repeater::{MAX_REPEATER_ROWS, parse_items, write_items};
48use topcoat::{Result, context::Cx, view::*};
49pub use tree::{IntoSchema, Source};
50pub(crate) use tree::{Node, Retype, render_nodes};
51
52use crate::values::{FieldErrors, FormValue, FormValues};
53
54pub struct Schema<F = ()> {
61 pub(crate) nodes: Vec<Node>,
62 pub(crate) fields: Vec<Field>,
63 form: PhantomData<fn() -> F>,
64}
65
66impl<F> Default for Schema<F> {
67 fn default() -> Self {
68 Self::from_parts(Vec::new(), Vec::new())
69 }
70}
71
72impl<F> std::fmt::Debug for Schema<F> {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.debug_struct("Schema")
75 .field("nodes", &self.nodes)
76 .field("fields", &self.fields)
77 .finish()
78 }
79}
80
81impl Schema {
82 pub fn empty() -> Self {
85 Self::default()
86 }
87}
88
89impl<F> Retype for Schema<F> {
90 type As<G> = Schema<G>;
91
92 fn retype<G>(self) -> Schema<G> {
93 Schema::from_parts(self.nodes, self.fields)
94 }
95}
96
97impl<F> Schema<F> {
98 pub(crate) fn from_parts(nodes: Vec<Node>, fields: Vec<Field>) -> Self {
99 Self {
100 nodes,
101 fields,
102 form: PhantomData,
103 }
104 }
105
106 pub fn is_empty(&self) -> bool {
108 self.nodes.is_empty()
109 }
110
111 pub fn new(children: impl IntoSchema<F>) -> Self {
114 children.into_schema()
115 }
116
117 pub fn fields(&self) -> impl Iterator<Item = &Field> {
120 self.fields.iter()
121 }
122
123 pub(crate) fn require(&mut self, required: &HashSet<&str>) {
125 for field in &mut self.fields {
126 let key = required.contains(field.name());
127 field.set_required(key);
128 }
129 }
130
131 pub async fn render<'a>(&self, cx: &'a Cx, source: Source<'_>) -> Result<BoxView<'a>> {
137 let errors = self.declaration_errors();
138 if !errors.is_empty() {
139 return Err(crate::error::misdeclared(&errors));
140 }
141 let source = source.watching(self.watched());
142 render_nodes(cx, &self.nodes, &self.fields, &source).await
143 }
144
145 pub(crate) fn append(&mut self, other: Schema<F>) {
147 let Schema {
148 mut nodes, fields, ..
149 } = other;
150 let offset = self.fields.len();
151 for node in &mut nodes {
152 node.offset(offset);
153 }
154 self.fields.extend(fields);
155 self.nodes.extend(nodes);
156 }
157
158 pub(crate) fn embedded_root(&self) -> &embedded::Embedded {
160 match self.nodes.as_slice() {
161 [Node::Embedded(node)] => node,
162 _ => panic!("an embedded value's schema is its one embedded node"),
163 }
164 }
165
166 pub(crate) fn defaults(&self) -> FormValues {
168 self.fields
169 .iter()
170 .filter_map(|field| Some((field.name().to_string(), field.default_value()?.clone())))
171 .collect()
172 }
173
174 pub(crate) fn defaults_of<'k>(&self, keys: impl Iterator<Item = &'k str>) -> FormValues {
176 let keys: Vec<&str> = keys.collect();
177 self.fields
178 .iter()
179 .filter(|field| keys.contains(&field.name()))
180 .filter_map(|field| Some((field.name().to_string(), field.default_value()?.clone())))
181 .collect()
182 }
183
184 pub(crate) fn drop_disabled(&self, values: &mut FormValues, editing: bool) {
188 for field in self
189 .fields
190 .iter()
191 .filter(|field| field.is_disabled(editing))
192 {
193 let name = field.name();
194 values.remove(name);
195 if let Some(default) = field.default_value()
196 && !editing
197 {
198 values.insert(name, default.clone());
199 }
200 }
201 }
202
203 pub(crate) fn disabled_keys(&self, editing: bool) -> impl Iterator<Item = &str> {
205 self.fields
206 .iter()
207 .filter(move |field| field.is_disabled(editing))
208 .map(Field::name)
209 }
210
211 pub fn extend(mut self, other: Schema<F>) -> Schema<F> {
213 self.append(other);
214 self
215 }
216
217 pub(crate) fn unplaced_error<'e>(&self, errors: &'e crate::FieldErrors) -> Option<&'e str> {
220 errors.iter().map(|error| error.key.as_str()).find(|key| {
221 !self
222 .fields
223 .iter()
224 .any(|field| match key.strip_prefix(field.name()) {
225 Some(rest) => {
226 rest.is_empty() || (rest.starts_with('.') && field.as_repeater().is_some())
227 }
228 None => false,
229 })
230 })
231 }
232
233 pub(crate) fn empty_choices(&self) -> Vec<crate::DeclarationErrorKind> {
238 self.fields
239 .iter()
240 .filter(|field| field.offers_nothing())
241 .map(|field| crate::DeclarationErrorKind::EmptyChoice {
242 field: field.name().to_string(),
243 })
244 .collect()
245 }
246
247 fn dependent_errors(&self) -> Vec<crate::DeclarationErrorKind> {
251 let mut errors = Vec::new();
252 for field in &self.fields {
253 let Some(choice) = field.as_choice() else {
254 continue;
255 };
256 if choice.misdeclared_parent() {
257 errors.push(crate::DeclarationErrorKind::MisdeclaredDependentChoice {
258 field: field.name().to_string(),
259 });
260 }
261 let Some(parent) = choice.parent_key() else {
262 continue;
263 };
264 match self.fields.iter().find(|placed| placed.name() == parent) {
265 None => errors.push(crate::DeclarationErrorKind::UnplacedParentField {
266 field: field.name().to_string(),
267 parent: parent.to_string(),
268 }),
269 Some(parent)
270 if choice.is_multiple()
271 || parent.as_choice().is_some_and(ChoiceControl::is_multiple) =>
272 {
273 errors.push(crate::DeclarationErrorKind::MultipleDependentChoice {
274 field: field.name().to_string(),
275 });
276 }
277 Some(_) if choice.is_listed() => {
278 errors.push(crate::DeclarationErrorKind::ListedDependentChoice {
279 field: field.name().to_string(),
280 });
281 }
282 Some(_) => {}
283 }
284 }
285 errors
286 }
287
288 pub(crate) fn watchers(&self) -> Vec<(String, Option<String>)> {
291 let mut watchers = self.condition_watchers();
292 watchers.extend(self.comparison_watchers());
293 watchers.extend(self.fields.iter().filter_map(|field| {
294 field
295 .parent_key()
296 .map(|parent| (parent.to_string(), Some(field.name().to_string())))
297 }));
298 watchers
299 }
300
301 pub fn declaration_errors(&self) -> Vec<crate::DeclarationErrorKind> {
304 let mut errors = self.condition_errors();
305 errors.extend(self.dependent_errors());
306 errors.extend(self.comparison_errors(""));
307 for field in &self.fields {
309 errors.extend(field.rule_errors(field.name()));
310 if let Some(repeater) = field.as_repeater() {
311 let item = repeater.item_schema();
312 for leaf in item.fields() {
313 errors.extend(leaf.rule_errors(&format!("{}.{}", field.name(), leaf.name())));
314 }
315 errors.extend(item.comparison_errors(&format!("{}.", field.name())));
316 }
317 let blank = field.default_value().is_none_or(|value| match value {
318 FormValue::Text(text) => text.trim().is_empty(),
319 FormValue::List(items) => items.is_empty(),
320 FormValue::Rows(rows) => rows.is_empty(),
321 });
322 if field.is_always_disabled() && field.is_required() && blank {
323 errors.push(crate::DeclarationErrorKind::DisabledWithoutValue {
324 field: field.name().to_string(),
325 });
326 }
327 if let Some(value) = field.default_value()
328 && field.refuses(value)
329 {
330 errors.push(crate::DeclarationErrorKind::UnpostedDefault {
331 field: field.name().to_string(),
332 value: match value {
333 FormValue::Text(text) => text.clone(),
334 FormValue::List(items) => format!("{items:?}"),
335 FormValue::Rows(_) => "rows".to_string(),
336 },
337 });
338 }
339 if let Some(repeater) = field.as_repeater()
342 && let Some(leaf) = repeater
343 .item_schema()
344 .fields()
345 .find(|leaf| leaf.is_disabled(true) || leaf.default_value().is_some())
346 {
347 errors.push(crate::DeclarationErrorKind::RepeaterLeafModifier {
348 field: field.name().to_string(),
349 leaf: leaf.name().to_string(),
350 });
351 }
352 if let Some(repeater) = field.as_repeater()
355 && let Some(leaf) = repeater
356 .item_schema()
357 .fields()
358 .find(|leaf| leaf.is_password())
359 {
360 errors.push(crate::DeclarationErrorKind::RepeaterPassword {
361 field: field.name().to_string(),
362 leaf: leaf.name().to_string(),
363 });
364 }
365 }
366 let mut seen = HashSet::new();
367 for field in &self.fields {
368 match field.misdeclared() {
369 Some(error) => errors.push(error),
370 None if !seen.insert(field.name()) => {
371 errors.push(crate::DeclarationErrorKind::DuplicateField {
372 name: field.name().to_string(),
373 });
374 }
375 None => {}
376 }
377 }
378 errors
379 }
380
381 pub(crate) fn hidden_fields(&self, values: &FormValues) -> HashSet<String> {
384 fn walk(nodes: &[Node], values: &FormValues, out: &mut Vec<usize>) {
385 for node in nodes {
386 match node {
387 Node::Embedded(embedded) => embedded.hidden_fields(values, out),
388 node => {
389 if let Some(children) = node.children() {
390 walk(children, values, out);
391 }
392 }
393 }
394 }
395 }
396 let mut indices = Vec::new();
397 walk(&self.nodes, values, &mut indices);
398 indices
399 .into_iter()
400 .map(|index| self.fields[index].name().to_string())
401 .chain(self.condition_hidden(values))
402 .collect()
403 }
404
405 pub(crate) async fn check_controls(
412 &self,
413 cx: &Cx,
414 values: &FormValues,
415 stored: &FormValues,
416 errors: &mut FieldErrors,
417 ) {
418 let hidden = self.hidden_fields(values);
419 let zone = crate::time_zone(cx);
420 for field in &self.fields {
421 let name = field.name();
422 if errors.contains_key(name) || hidden.contains(name) {
423 continue;
424 }
425 let Some(value) = values.get(name) else {
426 if let Some(error) = field.check_unposted(&zone) {
427 errors.push(error);
428 }
429 continue;
430 };
431 let refusal = (stored.get(name) != Some(value))
432 .then(|| field.check(value, &zone))
433 .flatten();
434 if let Some(repeater) = field.as_repeater() {
435 if let Some(error) = refusal {
437 errors.push(error);
438 }
439 let rows = value.as_rows().unwrap_or_default();
440 let held = stored.get(name).and_then(FormValue::as_rows);
441 repeater
442 .check(cx, name, rows, held.unwrap_or_default(), errors)
443 .await;
444 continue;
445 }
446 if let Some(error) =
447 refusal.or_else(|| self.comparison_refusal(field, values, stored, &hidden))
448 {
449 errors.push(error);
450 continue;
451 }
452 let parent = field.parent_key().and_then(|key| values.text(key));
453 for message in field
454 .validate_exists(cx, value, parent, stored.get(name))
455 .await
456 {
457 errors.add(name, message);
458 }
459 }
460 }
461
462 #[cfg(test)]
464 pub(crate) async fn checked(&self, cx: &Cx, values: &FormValues) -> FieldErrors {
465 let mut errors = FieldErrors::new();
466 self.check_controls(cx, values, &FormValues::new(), &mut errors)
467 .await;
468 errors
469 }
470
471 pub(crate) fn keep_unedited(
474 &self,
475 values: &mut FormValues,
476 stored: &FormValues,
477 zone: &jiff::tz::TimeZone,
478 ) {
479 for field in &self.fields {
480 let name = field.name();
481 let (Some(value), Some(held)) = (values.get(name), stored.get(name)) else {
482 continue;
483 };
484 if let Some(kept) = field.keep_unedited(value, held, zone) {
485 values.insert(name, kept);
486 }
487 }
488 }
489
490 pub(crate) async fn recheck_relationships(
493 &self,
494 cx: &Cx,
495 values: &FormValues,
496 stored: &FormValues,
497 ex: &mut dyn toasty::Executor,
498 ) -> FieldErrors {
499 let mut errors = FieldErrors::new();
500 let hidden = self.hidden_fields(values);
501 for field in &self.fields {
502 let name = field.name();
503 if hidden.contains(name) {
504 continue;
505 }
506 let Some(value) = values.get(name) else {
507 continue;
508 };
509 let parent = field.parent_key().and_then(|key| values.text(key));
510 for message in field
511 .recheck(cx, value, parent, stored.get(name), &mut *ex)
512 .await
513 {
514 errors.add(name, message);
515 }
516 }
517 errors
518 }
519
520 pub(crate) async fn keep_unseen_links(
524 &self,
525 cx: &Cx,
526 values: &mut FormValues,
527 stored: &FormValues,
528 ex: &mut dyn toasty::Executor,
529 ) {
530 for field in &self.fields {
531 let name = field.name();
532 let (Some(value), Some(held)) = (values.get(name), stored.get(name)) else {
533 continue;
534 };
535 if let Some(kept) = field.keep_unseen(cx, value, held, &mut *ex).await {
536 values.insert(name, kept);
537 }
538 }
539 }
540}
541
542#[cfg(test)]
543mod tests;