1mod condition;
7pub(crate) mod embedded;
8pub(crate) mod fields;
9mod layouts;
10mod lenses;
11mod options;
12mod read;
13pub(crate) mod relationship;
14pub(crate) mod repeater;
15pub(crate) mod tree;
16pub(crate) mod validation;
17
18use std::{collections::HashSet, marker::PhantomData};
19
20pub use condition::Watched;
21pub use embedded::EmbeddedForm;
22use fields::ChoiceControl;
23pub use fields::{
24 ChoiceField, CustomField, Field, FileField, IntoFormValue, IntoOptions, RepeaterField,
25 TextField, Toggle,
26};
27pub(crate) use fields::{option_view, read_only, stored_upload, value_cell};
28pub use layouts::{Grid, Group, Section};
29pub(crate) use lenses::Binding;
30pub use lenses::{FieldResolver, form_key};
31pub use options::Options;
32pub use relationship::MAX_RELATIONSHIP_OPTIONS;
33pub(crate) use relationship::{OptionLoadError, OptionSource};
34pub use repeater::{MAX_ROWS, RepeaterItem, parse_items, write_items};
35use topcoat::{Result, context::Cx, view::*};
36pub use tree::{IntoSchema, Source};
37pub(crate) use tree::{Node, Retype, render_nodes};
38use tree::{bind_nodes, unbound_values};
39pub(crate) use validation::TypedValue;
40
41use crate::form::{FieldErrors, FormValue, FormValues};
42
43pub struct Schema<F = ()> {
50 pub(crate) nodes: Vec<Node>,
51 pub(crate) fields: Vec<Field>,
52 form: PhantomData<fn() -> F>,
53}
54
55impl<F> Default for Schema<F> {
56 fn default() -> Self {
57 Self::from_parts(Vec::new(), Vec::new())
58 }
59}
60
61impl<F> std::fmt::Debug for Schema<F> {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("Schema")
64 .field("nodes", &self.nodes)
65 .field("fields", &self.fields)
66 .finish()
67 }
68}
69
70impl Schema {
71 pub fn empty() -> Self {
74 Self::default()
75 }
76}
77
78impl<F> Retype for Schema<F> {
79 type As<G> = Schema<G>;
80
81 fn retype<G>(self) -> Schema<G> {
82 Schema::from_parts(self.nodes, self.fields)
83 }
84}
85
86impl<F> Schema<F> {
87 pub(crate) fn from_parts(nodes: Vec<Node>, fields: Vec<Field>) -> Self {
88 Self {
89 nodes,
90 fields,
91 form: PhantomData,
92 }
93 }
94
95 pub fn is_empty(&self) -> bool {
97 self.nodes.is_empty()
98 }
99
100 pub fn new(children: impl IntoSchema<F>) -> Self {
103 children.into_schema()
104 }
105
106 pub fn fields(&self) -> impl Iterator<Item = &Field> {
109 self.fields.iter()
110 }
111
112 pub fn bind(mut self, db: &toasty::Db) -> Self {
117 self.bind_with(&FieldResolver::of_db(db));
118 self
119 }
120
121 pub(crate) fn bind_with(&mut self, resolver: &FieldResolver) {
124 bind_nodes(&mut self.nodes, &mut self.fields, resolver);
125 for field in &self.fields {
126 field.bind(resolver);
127 }
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 drop_disabled(&self, values: &mut FormValues, editing: bool) {
185 for field in self
186 .fields
187 .iter()
188 .filter(|field| field.is_disabled(editing))
189 {
190 let name = field.name();
191 values.remove(name);
192 if let Some(default) = field.default_value()
193 && !editing
194 {
195 values.insert(name, default.clone());
196 }
197 }
198 }
199
200 pub(crate) fn disabled_keys(&self, editing: bool) -> impl Iterator<Item = &str> {
202 self.fields
203 .iter()
204 .filter(move |field| field.is_disabled(editing))
205 .map(Field::name)
206 }
207
208 pub fn extend(mut self, other: Schema<F>) -> Schema<F> {
210 self.append(other);
211 self
212 }
213
214 pub(crate) fn unplaced_error<'e>(&self, errors: &'e crate::FieldErrors) -> Option<&'e str> {
217 errors.iter().map(|error| error.key.as_str()).find(|key| {
218 !self
219 .fields
220 .iter()
221 .any(|field| match key.strip_prefix(field.name()) {
222 Some(rest) => {
223 rest.is_empty() || (rest.starts_with('.') && field.as_repeater().is_some())
224 }
225 None => false,
226 })
227 })
228 }
229
230 pub(crate) fn empty_choices(&self) -> Vec<crate::DeclarationErrorKind> {
235 self.fields
236 .iter()
237 .filter(|field| field.offers_nothing())
238 .map(|field| crate::DeclarationErrorKind::EmptyChoice {
239 field: field.name().to_string(),
240 })
241 .collect()
242 }
243
244 fn dependent_errors(&self) -> Vec<crate::DeclarationErrorKind> {
248 let mut errors = Vec::new();
249 for field in &self.fields {
250 let Some(choice) = field.as_choice() else {
251 continue;
252 };
253 if choice.misdeclared_parent() {
254 errors.push(crate::DeclarationErrorKind::MisdeclaredDependentChoice {
255 field: field.name().to_string(),
256 });
257 }
258 let Some(parent) = choice.parent_key() else {
259 continue;
260 };
261 match self.fields.iter().find(|placed| placed.name() == parent) {
262 None => errors.push(crate::DeclarationErrorKind::UnplacedParentField {
263 field: field.name().to_string(),
264 parent: parent.to_string(),
265 }),
266 Some(parent)
267 if choice.is_multiple()
268 || parent.as_choice().is_some_and(ChoiceControl::is_multiple) =>
269 {
270 errors.push(crate::DeclarationErrorKind::MultipleDependentChoice {
271 field: field.name().to_string(),
272 });
273 }
274 Some(_) => {}
275 }
276 }
277 errors
278 }
279
280 pub fn declaration_errors(&self) -> Vec<crate::DeclarationErrorKind> {
283 let mut unbound = Vec::new();
284 unbound_values(&self.nodes, &mut unbound);
285 let mut errors: Vec<_> = unbound
286 .into_iter()
287 .map(|item| crate::DeclarationErrorKind::Unbound { item })
288 .collect();
289 errors.extend(self.condition_errors());
290 errors.extend(self.dependent_errors());
291 for field in &self.fields {
293 let blank = field.default_value().is_none_or(|value| match value {
294 FormValue::Text(text) => text.trim().is_empty(),
295 FormValue::List(items) => items.is_empty(),
296 FormValue::Rows(rows) => rows.is_empty(),
297 });
298 if field.is_always_disabled() && field.is_required() && blank {
299 errors.push(crate::DeclarationErrorKind::DisabledWithoutValue {
300 field: field.name().to_string(),
301 });
302 }
303 if let Some(value) = field.default_value()
304 && field.refuses(value)
305 {
306 errors.push(crate::DeclarationErrorKind::UnpostedDefault {
307 field: field.name().to_string(),
308 value: match value {
309 FormValue::Text(text) => text.clone(),
310 FormValue::List(items) => format!("{items:?}"),
311 FormValue::Rows(_) => "rows".to_string(),
312 },
313 });
314 }
315 if let Some(repeater) = field.as_repeater()
318 && let Some(leaf) = repeater
319 .item_schema()
320 .fields()
321 .find(|leaf| leaf.is_disabled(true) || leaf.default_value().is_some())
322 {
323 errors.push(crate::DeclarationErrorKind::RepeaterLeafModifier {
324 field: field.name().to_string(),
325 leaf: leaf.name().to_string(),
326 });
327 }
328 }
329 let mut seen = HashSet::new();
330 for field in &self.fields {
331 match field.misdeclared() {
332 Some(error) => errors.push(error),
333 None if !seen.insert(field.name()) => {
334 errors.push(crate::DeclarationErrorKind::DuplicateField {
335 name: field.name().to_string(),
336 });
337 }
338 None => {}
339 }
340 }
341 errors
342 }
343
344 pub(crate) fn hidden_fields(&self, values: &FormValues) -> HashSet<String> {
347 fn walk(nodes: &[Node], values: &FormValues, out: &mut Vec<usize>) {
348 for node in nodes {
349 match node {
350 Node::Embedded(embedded) => embedded.hidden_fields(values, out),
351 node => {
352 if let Some(children) = node.children() {
353 walk(children, values, out);
354 }
355 }
356 }
357 }
358 }
359 let mut indices = Vec::new();
360 walk(&self.nodes, values, &mut indices);
361 indices
362 .into_iter()
363 .map(|index| self.fields[index].name().to_string())
364 .chain(self.condition_hidden(values))
365 .collect()
366 }
367
368 pub(crate) async fn check_controls(
372 &self,
373 cx: &Cx,
374 values: &FormValues,
375 stored: &FormValues,
376 errors: &mut FieldErrors,
377 ) {
378 let hidden = self.hidden_fields(values);
379 for field in &self.fields {
380 let name = field.name();
381 if errors.contains_key(name) || hidden.contains(name) {
382 continue;
383 }
384 let Some(value) = values.get(name) else {
385 continue;
386 };
387 if let Some(repeater) = field.as_repeater() {
388 let rows = value.as_rows().unwrap_or_default();
389 repeater.check(cx, name, rows, errors).await;
390 continue;
391 }
392 if let Some(error) = field.check(value) {
393 errors.push(error);
394 continue;
395 }
396 let parent = field.parent_key().and_then(|key| values.text(key));
397 for message in field
398 .validate_exists(cx, value, parent, stored.get(name))
399 .await
400 {
401 errors.add(name, message);
402 }
403 }
404 }
405
406 #[cfg(test)]
408 pub(crate) async fn checked(&self, cx: &Cx, values: &FormValues) -> FieldErrors {
409 let mut errors = FieldErrors::new();
410 self.check_controls(cx, values, &FormValues::new(), &mut errors)
411 .await;
412 errors
413 }
414
415 pub(crate) async fn recheck_relationships(
418 &self,
419 cx: &Cx,
420 values: &FormValues,
421 stored: &FormValues,
422 ex: &mut dyn toasty::Executor,
423 ) -> FieldErrors {
424 let mut errors = FieldErrors::new();
425 let hidden = self.hidden_fields(values);
426 for field in &self.fields {
427 let name = field.name();
428 if hidden.contains(name) {
429 continue;
430 }
431 let Some(value) = values.get(name) else {
432 continue;
433 };
434 let parent = field.parent_key().and_then(|key| values.text(key));
435 for message in field
436 .recheck(cx, value, parent, stored.get(name), &mut *ex)
437 .await
438 {
439 errors.add(name, message);
440 }
441 }
442 errors
443 }
444
445 pub(crate) async fn keep_unseen_links(
449 &self,
450 cx: &Cx,
451 values: &mut FormValues,
452 stored: &FormValues,
453 ex: &mut dyn toasty::Executor,
454 ) {
455 for field in &self.fields {
456 let name = field.name();
457 let (Some(value), Some(held)) = (values.get(name), stored.get(name)) else {
458 continue;
459 };
460 if let Some(kept) = field.keep_unseen(cx, value, held, &mut *ex).await {
461 values.insert(name, kept);
462 }
463 }
464 }
465}
466
467#[cfg(test)]
468mod tests;