1pub(crate) mod embedded;
7mod fields;
8mod layouts;
9mod lenses;
10mod options;
11mod relationship;
12mod tree;
13mod validation;
14
15use std::collections::{HashMap, HashSet};
16
17pub use embedded::EmbeddedForm;
18pub(crate) use fields::option_view;
19pub use fields::{
20 ChoiceField, Control, ControlInput, CustomField, Field, FileField, IntoOptions, TextField,
21 Toggle,
22};
23pub use layouts::{Grid, Group, Section};
24pub(crate) use lenses::Binding;
25pub use lenses::{FieldResolver, form_key};
26pub use options::Options;
27pub(crate) use relationship::OptionLoadError;
28pub use relationship::{MAX_RELATIONSHIP_OPTIONS, OptionSource};
29use topcoat::{Result, context::Cx, view::*};
30pub use tree::{IntoSchema, Source};
31pub(crate) use tree::{Node, render_nodes};
32use tree::{bind_nodes, unbound_values};
33pub use validation::TypedValue;
34
35use crate::form::FieldErrors;
36
37#[derive(Debug, Default)]
39pub struct Schema {
40 pub(crate) nodes: Vec<Node>,
41 pub(crate) fields: Vec<Field>,
42}
43
44impl Schema {
45 pub fn is_empty(&self) -> bool {
47 self.nodes.is_empty()
48 }
49
50 pub fn new(children: impl IntoSchema) -> Self {
53 children.into_schema()
54 }
55
56 pub fn empty() -> Self {
57 Self::default()
58 }
59
60 pub fn fields(&self) -> impl Iterator<Item = &Field> {
63 self.fields.iter()
64 }
65
66 pub fn bind(mut self, db: &toasty::Db) -> Self {
71 self.bind_with(&FieldResolver::of_db(db));
72 self
73 }
74
75 pub(crate) fn bind_with(&mut self, resolver: &FieldResolver) {
78 bind_nodes(&mut self.nodes, &mut self.fields, resolver);
79 for field in &self.fields {
80 field.bind(resolver);
81 }
82 }
83
84 pub(crate) fn require(&mut self, required: &HashSet<&str>) {
86 for field in &mut self.fields {
87 let key = required.contains(field.name());
88 field.set_required(key);
89 }
90 }
91
92 pub async fn render<'a>(&self, cx: &'a Cx, source: Source<'_>) -> Result<BoxView<'a>> {
98 let errors = self.declaration_errors();
99 if !errors.is_empty() {
100 return Err(crate::error::misdeclared(&errors));
101 }
102 render_nodes(cx, &self.nodes, &self.fields, &source).await
103 }
104
105 pub(crate) fn append(&mut self, other: Schema) {
107 let Schema { mut nodes, fields } = other;
108 let offset = self.fields.len();
109 for node in &mut nodes {
110 node.offset(offset);
111 }
112 self.fields.extend(fields);
113 self.nodes.extend(nodes);
114 }
115
116 pub(crate) fn embedded_root(&self) -> &embedded::Embedded {
118 match self.nodes.as_slice() {
119 [Node::Embedded(node)] => node,
120 _ => panic!("an embedded value's schema is its one embedded node"),
121 }
122 }
123
124 pub fn extend(mut self, other: Schema) -> Schema {
126 self.append(other);
127 self
128 }
129
130 pub(crate) fn unknown_keys(&self, values: &HashMap<String, String>) -> Vec<String> {
133 let known: HashSet<&str> = self.fields.iter().map(Field::name).collect();
134 let mut out: Vec<String> = values
135 .keys()
136 .filter(|k| !known.contains(k.as_str()))
137 .cloned()
138 .collect();
139 out.sort();
140 out
141 }
142
143 pub fn declaration_errors(&self) -> Vec<crate::DeclarationErrorKind> {
146 let mut unbound = Vec::new();
147 unbound_values(&self.nodes, &mut unbound);
148 let mut errors: Vec<_> = unbound
149 .into_iter()
150 .map(|item| crate::DeclarationErrorKind::Unbound { item })
151 .collect();
152 let mut seen = HashSet::new();
153 for field in &self.fields {
154 match field.misdeclared() {
155 Some(error) => errors.push(error),
156 None if !seen.insert(field.name()) => {
157 errors.push(crate::DeclarationErrorKind::DuplicateField {
158 name: field.name().to_string(),
159 });
160 }
161 None => {}
162 }
163 }
164 errors
165 }
166
167 pub(crate) fn hidden_fields(&self, values: &HashMap<String, String>) -> HashSet<String> {
170 fn walk(nodes: &[Node], values: &HashMap<String, String>, out: &mut Vec<usize>) {
171 for node in nodes {
172 match node {
173 Node::Embedded(embedded) => embedded.hidden_fields(values, out),
174 node => {
175 if let Some(children) = node.children() {
176 walk(children, values, out);
177 }
178 }
179 }
180 }
181 }
182 let mut indices = Vec::new();
183 walk(&self.nodes, values, &mut indices);
184 indices
185 .into_iter()
186 .map(|index| self.fields[index].name().to_string())
187 .collect()
188 }
189
190 pub(crate) async fn check_controls(
194 &self,
195 cx: &Cx,
196 values: &HashMap<String, String>,
197 errors: &mut FieldErrors,
198 ) {
199 let hidden = self.hidden_fields(values);
200 for field in &self.fields {
201 let name = field.name();
202 if errors.contains_key(name) || hidden.contains(name) {
203 continue;
204 }
205 let Some(value) = values.get(name) else {
206 continue;
207 };
208 if let Some(error) = field.check(value) {
209 errors.push(error);
210 continue;
211 }
212 for message in field.validate_exists(cx, value).await {
213 errors.add(name, message);
214 }
215 }
216 }
217
218 #[cfg(test)]
220 pub(crate) async fn checked(&self, cx: &Cx, values: &HashMap<String, String>) -> FieldErrors {
221 let mut errors = FieldErrors::new();
222 self.check_controls(cx, values, &mut errors).await;
223 errors
224 }
225
226 pub(crate) async fn recheck_relationships(
228 &self,
229 cx: &Cx,
230 values: &HashMap<String, String>,
231 ex: &mut dyn toasty::Executor,
232 ) -> FieldErrors {
233 let mut errors = FieldErrors::new();
234 let hidden = self.hidden_fields(values);
235 for field in &self.fields {
236 let name = field.name();
237 if hidden.contains(name) {
238 continue;
239 }
240 let Some(value) = values.get(name) else {
241 continue;
242 };
243 for message in field.recheck(cx, value, &mut *ex).await {
244 errors.add(name, message);
245 }
246 }
247 errors
248 }
249}
250
251#[cfg(test)]
252mod tests;