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