1mod comparison;
7mod condition;
8mod creatable;
9pub(crate) mod embedded;
10pub(crate) mod fields;
11pub(crate) mod input;
12mod layouts;
13mod lenses;
14mod mask;
15mod options;
16mod read;
17pub(crate) mod relationship;
18pub(crate) mod repeater;
19pub(crate) mod tree;
20
21use std::{collections::HashSet, marker::PhantomData};
22
23pub use condition::Watched;
24pub use embedded::EmbeddedForm;
25use fields::ChoiceControl;
26#[cfg(feature = "rich-text")]
27pub use fields::RichTextField;
28pub use fields::{
29 ChoiceField, CustomField, Field, FileField, IntoFormValue, IntoOptions, RepeaterField, Switch,
30 TextField, Toggle,
31};
32pub(crate) use fields::{
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, Step, Tab, Tabs, Wizard};
38pub(crate) use layouts::{TabView, tab_id, tab_view, titled};
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
54#[doc(hidden)]
59pub trait EditsRecords {}
60
61pub struct Schema<F = ()> {
68 pub(crate) nodes: Vec<Node>,
69 pub(crate) fields: Vec<Field>,
70 form: PhantomData<fn() -> F>,
71}
72
73impl<F> Default for Schema<F> {
74 fn default() -> Self {
75 Self::from_parts(Vec::new(), Vec::new())
76 }
77}
78
79impl<F> std::fmt::Debug for Schema<F> {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("Schema")
82 .field("nodes", &self.nodes)
83 .field("fields", &self.fields)
84 .finish()
85 }
86}
87
88impl Schema {
89 pub fn empty() -> Self {
92 Self::default()
93 }
94}
95
96impl<F> Retype for Schema<F> {
97 type As<G> = Schema<G>;
98
99 fn retype<G>(self) -> Schema<G> {
100 Schema::from_parts(self.nodes, self.fields)
101 }
102}
103
104impl<F> Schema<F> {
105 pub(crate) fn from_parts(nodes: Vec<Node>, fields: Vec<Field>) -> Self {
106 Self {
107 nodes,
108 fields,
109 form: PhantomData,
110 }
111 }
112
113 pub fn is_empty(&self) -> bool {
115 self.nodes.is_empty()
116 }
117
118 pub fn new(children: impl IntoSchema<F>) -> Self {
121 children.into_schema()
122 }
123
124 pub fn fields(&self) -> impl Iterator<Item = &Field> {
127 self.fields.iter()
128 }
129
130 pub(crate) fn require(&mut self, required: &HashSet<&str>) {
132 for field in &mut self.fields {
133 let key = required.contains(field.name());
134 field.set_required(key);
135 }
136 }
137
138 pub async fn render<'a>(&self, cx: &'a Cx, source: Source<'_>) -> Result<BoxView<'a>> {
144 let errors = self.declaration_errors();
145 if !errors.is_empty() {
146 return Err(crate::error::misdeclared(&errors));
147 }
148 let source = source.watching(self.watched());
149 render_nodes(cx, &self.nodes, &self.fields, &source).await
150 }
151
152 pub(crate) fn append(&mut self, other: Schema<F>) {
154 let Schema {
155 mut nodes, fields, ..
156 } = other;
157 let offset = self.fields.len();
158 for node in &mut nodes {
159 node.offset(offset);
160 }
161 self.fields.extend(fields);
162 self.nodes.extend(nodes);
163 }
164
165 pub(crate) fn embedded_root(&self) -> &embedded::Embedded {
167 match self.nodes.as_slice() {
168 [Node::Embedded(node)] => node,
169 _ => panic!("an embedded value's schema is its one embedded node"),
170 }
171 }
172
173 pub(crate) fn defaults(&self) -> FormValues {
175 self.fields
176 .iter()
177 .filter_map(|field| Some((field.name().to_string(), field.default_value()?.clone())))
178 .collect()
179 }
180
181 pub(crate) fn defaults_of<'k>(&self, keys: impl Iterator<Item = &'k str>) -> FormValues {
183 let keys: Vec<&str> = keys.collect();
184 self.fields
185 .iter()
186 .filter(|field| keys.contains(&field.name()))
187 .filter_map(|field| Some((field.name().to_string(), field.default_value()?.clone())))
188 .collect()
189 }
190
191 pub(crate) fn drop_disabled(&self, values: &mut FormValues, editing: bool) {
196 for field in self
197 .fields
198 .iter()
199 .filter(|field| field.is_disabled(editing))
200 {
201 let name = field.name();
202 values.remove(name);
203 if let Some(default) = field.default_value()
204 && !editing
205 {
206 values.insert(name, default.clone());
207 }
208 }
209 }
210
211 pub(crate) fn disabled_keys(&self, editing: bool) -> impl Iterator<Item = &str> {
213 self.fields
214 .iter()
215 .filter(move |field| field.is_disabled(editing))
216 .map(Field::name)
217 }
218
219 pub fn extend(mut self, other: Schema<F>) -> Schema<F> {
221 self.append(other);
222 self
223 }
224
225 pub(crate) fn unplaced_error<'e>(&self, errors: &'e crate::FieldErrors) -> Option<&'e str> {
228 errors.iter().map(|error| error.key.as_str()).find(|key| {
229 !self
230 .fields
231 .iter()
232 .any(|field| match key.strip_prefix(field.name()) {
233 Some(rest) => {
234 rest.is_empty() || (rest.starts_with('.') && field.as_repeater().is_some())
235 }
236 None => false,
237 })
238 })
239 }
240
241 pub(crate) fn empty_choices(&self) -> Vec<crate::DeclarationErrorKind> {
246 self.fields
247 .iter()
248 .filter(|field| field.offers_nothing())
249 .map(|field| crate::DeclarationErrorKind::EmptyChoice {
250 field: field.name().to_string(),
251 })
252 .collect()
253 }
254
255 fn dependent_errors(&self) -> Vec<crate::DeclarationErrorKind> {
259 let mut errors = Vec::new();
260 for field in &self.fields {
261 let Some(choice) = field.as_choice() else {
262 continue;
263 };
264 if choice.misdeclared_parent() {
265 errors.push(crate::DeclarationErrorKind::MisdeclaredDependentChoice {
266 field: field.name().to_string(),
267 });
268 }
269 if choice.misdeclared_filter() {
270 errors.push(crate::DeclarationErrorKind::UnfilterableChoice {
271 field: field.name().to_string(),
272 });
273 }
274 let Some(parent) = choice.parent_key() else {
275 continue;
276 };
277 match self.fields.iter().find(|placed| placed.name() == parent) {
278 None => errors.push(crate::DeclarationErrorKind::UnplacedParentField {
279 field: field.name().to_string(),
280 parent: parent.to_string(),
281 }),
282 Some(parent)
283 if choice.is_multiple()
284 || parent.as_choice().is_some_and(ChoiceControl::is_multiple) =>
285 {
286 errors.push(crate::DeclarationErrorKind::MultipleDependentChoice {
287 field: field.name().to_string(),
288 });
289 }
290 Some(_) if choice.is_listed() => {
291 errors.push(crate::DeclarationErrorKind::ListedDependentChoice {
292 field: field.name().to_string(),
293 });
294 }
295 Some(parent) if parent.disabling().is_some() => {
296 errors.push(crate::DeclarationErrorKind::DisabledWatchedField {
297 field: parent.name().to_string(),
298 });
299 }
300 Some(_) => {}
301 }
302 }
303 errors
304 }
305
306 pub(crate) fn watchers(&self) -> Vec<(String, Option<String>)> {
309 let mut watchers = self.condition_watchers();
310 watchers.extend(self.comparison_watchers());
311 watchers.extend(self.fields.iter().filter_map(|field| {
312 field
313 .parent_key()
314 .map(|parent| (parent.to_string(), Some(field.name().to_string())))
315 }));
316 watchers
317 }
318
319 pub(crate) fn file_caps(&self) -> Vec<(String, usize)> {
322 let cap = |field: &Field, name: String| {
323 field
324 .as_file()
325 .and_then(|file| file.max_bytes)
326 .map(|max| (name, max))
327 };
328 let mut caps = Vec::new();
329 for field in &self.fields {
330 caps.extend(cap(field, field.name().to_string()));
331 if let Some(repeater) = field.as_repeater() {
332 caps.extend(
333 repeater
334 .item_schema()
335 .fields()
336 .filter_map(|leaf| cap(leaf, format!("{}.{}", field.name(), leaf.name()))),
337 );
338 }
339 }
340 caps
341 }
342
343 pub fn declaration_errors(&self) -> Vec<crate::DeclarationErrorKind> {
346 let mut errors = self.condition_errors();
347 errors.extend(self.dependent_errors());
348 errors.extend(self.creatable_errors());
349 errors.extend(self.comparison_errors(""));
350 for field in &self.fields {
352 errors.extend(field.rule_errors(field.name()));
353 if let Some(repeater) = field.as_repeater() {
354 let item = repeater.item_schema();
355 for leaf in item.fields() {
356 errors.extend(leaf.rule_errors(&format!("{}.{}", field.name(), leaf.name())));
357 }
358 errors.extend(item.comparison_errors(&format!("{}.", field.name())));
359 }
360 let blank = field.default_value().is_none_or(|value| match value {
361 FormValue::Text(text) => text.trim().is_empty(),
362 FormValue::List(items) => items.is_empty(),
363 FormValue::Rows(rows) => rows.is_empty(),
364 });
365 if field.may_post_nothing_on_create() && field.is_required() && blank {
366 errors.push(crate::DeclarationErrorKind::DisabledWithoutValue {
367 field: field.name().to_string(),
368 });
369 }
370 if let Some(value) = field.default_value()
371 && field.refuses(value)
372 {
373 errors.push(crate::DeclarationErrorKind::UnpostedDefault {
374 field: field.name().to_string(),
375 value: match value {
376 FormValue::Text(text) => text.clone(),
377 FormValue::List(items) => format!("{items:?}"),
378 FormValue::Rows(_) => "rows".to_string(),
379 },
380 });
381 }
382 if let Some(repeater) = field.as_repeater()
385 && let Some(leaf) = repeater.item_schema().fields().find(|leaf| {
386 leaf.is_disabled(true)
387 || leaf.disabling().is_some()
388 || leaf.default_value().is_some()
389 })
390 {
391 errors.push(crate::DeclarationErrorKind::RepeaterLeafModifier {
392 field: field.name().to_string(),
393 leaf: leaf.name().to_string(),
394 });
395 }
396 if let Some(repeater) = field.as_repeater()
399 && let Some(leaf) = repeater
400 .item_schema()
401 .fields()
402 .find(|leaf| leaf.is_password())
403 {
404 errors.push(crate::DeclarationErrorKind::RepeaterPassword {
405 field: field.name().to_string(),
406 leaf: leaf.name().to_string(),
407 });
408 }
409 }
410 let mut seen = HashSet::new();
411 for field in &self.fields {
412 match field.misdeclared() {
413 Some(error) => errors.push(error),
414 None if !seen.insert(field.name()) => {
415 errors.push(crate::DeclarationErrorKind::DuplicateField {
416 name: field.name().to_string(),
417 });
418 }
419 None => {}
420 }
421 }
422 errors
423 }
424
425 pub(crate) fn hidden_fields(&self, values: &FormValues) -> HashSet<String> {
428 fn walk(nodes: &[Node], values: &FormValues, out: &mut Vec<usize>) {
429 for node in nodes {
430 match node {
431 Node::Embedded(embedded) => embedded.hidden_fields(values, out),
432 node => {
433 if let Some(children) = node.children() {
434 walk(children, values, out);
435 }
436 }
437 }
438 }
439 }
440 let mut indices = Vec::new();
441 walk(&self.nodes, values, &mut indices);
442 indices
443 .into_iter()
444 .map(|index| self.fields[index].name().to_string())
445 .chain(self.condition_hidden(values))
446 .collect()
447 }
448
449 pub(crate) async fn check_controls(
456 &self,
457 cx: &Cx,
458 values: &FormValues,
459 stored: &FormValues,
460 errors: &mut FieldErrors,
461 ) {
462 let hidden = self.hidden_fields(values);
463 let zone = crate::time_zone(cx);
464 for field in &self.fields {
465 let name = field.name();
466 if errors.contains_key(name) || hidden.contains(name) {
467 continue;
468 }
469 let Some(value) = values.get(name) else {
470 if let Some(error) = field.check_unposted(&zone) {
471 errors.push(error);
472 }
473 continue;
474 };
475 let refusal = (stored.get(name) != Some(value))
476 .then(|| field.check(value, &zone))
477 .flatten();
478 if let Some(repeater) = field.as_repeater() {
479 if let Some(error) = refusal {
481 errors.push(error);
482 }
483 let rows = value.as_rows().unwrap_or_default();
484 let held = stored.get(name).and_then(FormValue::as_rows);
485 repeater
486 .check(cx, name, rows, held.unwrap_or_default(), errors)
487 .await;
488 continue;
489 }
490 if let Some(error) =
491 refusal.or_else(|| self.comparison_refusal(field, values, stored, &hidden))
492 {
493 errors.push(error);
494 continue;
495 }
496 let parent = field.parent_key().and_then(|key| values.text(key));
497 for message in field
498 .validate_exists(cx, value, parent, stored.get(name))
499 .await
500 {
501 errors.add(name, message);
502 }
503 }
504 }
505
506 #[cfg(test)]
508 pub(crate) async fn checked(&self, cx: &Cx, values: &FormValues) -> FieldErrors {
509 let mut errors = FieldErrors::new();
510 self.check_controls(cx, values, &FormValues::new(), &mut errors)
511 .await;
512 errors
513 }
514
515 pub(crate) fn keep_unedited(
518 &self,
519 values: &mut FormValues,
520 stored: &FormValues,
521 zone: &jiff::tz::TimeZone,
522 ) {
523 for field in &self.fields {
524 let name = field.name();
525 let (Some(value), Some(held)) = (values.get(name), stored.get(name)) else {
526 continue;
527 };
528 if let Some(kept) = field.keep_unedited(value, held, zone) {
529 values.insert(name, kept);
530 }
531 }
532 }
533
534 pub(crate) async fn recheck_relationships(
537 &self,
538 cx: &Cx,
539 values: &FormValues,
540 stored: &FormValues,
541 ex: &mut dyn toasty::Executor,
542 ) -> FieldErrors {
543 let mut errors = FieldErrors::new();
544 let hidden = self.hidden_fields(values);
545 for field in &self.fields {
546 let name = field.name();
547 if hidden.contains(name) {
548 continue;
549 }
550 let Some(value) = values.get(name) else {
551 continue;
552 };
553 let parent = field.parent_key().and_then(|key| values.text(key));
554 for message in field
555 .recheck(cx, value, parent, stored.get(name), &mut *ex)
556 .await
557 {
558 errors.add(name, message);
559 }
560 }
561 errors
562 }
563
564 pub(crate) async fn keep_unseen_links(
568 &self,
569 cx: &Cx,
570 values: &mut FormValues,
571 stored: &FormValues,
572 ex: &mut dyn toasty::Executor,
573 ) {
574 for field in &self.fields {
575 let name = field.name();
576 let (Some(value), Some(held)) = (values.get(name), stored.get(name)) else {
577 continue;
578 };
579 if let Some(kept) = field.keep_unseen(cx, value, held, &mut *ex).await {
580 values.insert(name, kept);
581 }
582 }
583 }
584}
585
586#[cfg(test)]
587mod tests;