1use std::collections::{HashMap, HashSet};
4
5use topcoat::{Result, context::Cx, view::*};
6
7use super::{
8 Schema,
9 embedded::Embedded,
10 fields::Field,
11 layouts::{Grid, Group, Repeater, Section},
12 validation::required_error,
13};
14use crate::form::FieldErrors;
15
16#[derive(Debug)]
17pub(crate) enum Node {
18 Field(usize),
20 Repeater(Box<Repeater>),
21 Section(Box<Section>),
22 Group(Box<Group>),
23 Grid(Box<Grid>),
24 Embedded(Box<Embedded>),
25}
26
27#[derive(Clone, Copy, Debug)]
29pub(crate) enum LeafPlace {
30 Rendered,
31 Payload,
32 Discriminant,
33}
34
35pub struct Source<'a> {
37 values: &'a HashMap<String, String>,
38 errors: Option<&'a FieldErrors>,
39}
40
41impl<'a> Source<'a> {
42 pub fn form(values: &'a HashMap<String, String>, errors: &'a FieldErrors) -> Self {
44 Self {
45 values,
46 errors: Some(errors),
47 }
48 }
49
50 pub fn view(values: &'a HashMap<String, String>) -> Self {
52 Self {
53 values,
54 errors: None,
55 }
56 }
57
58 pub(crate) fn mode(&self) -> Mode {
59 if self.errors.is_some() {
60 Mode::Form
61 } else {
62 Mode::View
63 }
64 }
65
66 pub(crate) fn value(&self, name: &str) -> Option<&str> {
67 self.values.get(name).map(String::as_str)
68 }
69
70 pub(crate) fn errors_for(&self, name: &str) -> Option<&str> {
72 self.errors
73 .and_then(|errors| errors.first(name))
74 .map(|error| error.message.as_str())
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub(crate) enum Mode {
81 Form,
82 View,
83}
84
85impl Node {
86 pub(crate) async fn render<'a>(
88 &self,
89 cx: &'a Cx,
90 fields: &[Field],
91 source: &Source<'_>,
92 ) -> Result<BoxView<'a>> {
93 match self {
94 Node::Field(index) => {
95 let field = &fields[*index];
96 Box::pin(field.render(
97 cx,
98 source.value(field.name()),
99 source.errors_for(field.name()),
100 source.mode(),
101 ))
102 .await
103 }
104 Node::Repeater(r) => Box::pin(r.render(cx, fields, source)).await,
105 Node::Section(s) => Box::pin(s.render(cx, fields, source)).await,
106 Node::Group(g) => Box::pin(g.render(cx, fields, source)).await,
107 Node::Grid(g) => Box::pin(g.render(cx, fields, source)).await,
108 Node::Embedded(e) => Box::pin(e.render(cx, fields, source)).await,
109 }
110 }
111
112 pub(crate) fn children(&self) -> Option<&[Node]> {
115 match self {
116 Node::Repeater(r) => Some(&r.children.nodes),
117 Node::Section(s) => Some(&s.children.nodes),
118 Node::Group(g) => Some(&g.children.nodes),
119 Node::Grid(g) => Some(&g.children.nodes),
120 Node::Field(_) | Node::Embedded(_) => None,
121 }
122 }
123
124 fn children_mut(&mut self) -> Option<&mut [Node]> {
125 match self {
126 Node::Repeater(r) => Some(&mut r.children.nodes),
127 Node::Section(s) => Some(&mut s.children.nodes),
128 Node::Group(g) => Some(&mut g.children.nodes),
129 Node::Grid(g) => Some(&mut g.children.nodes),
130 Node::Field(_) | Node::Embedded(_) => None,
131 }
132 }
133
134 pub(crate) fn offset(&mut self, by: usize) {
136 match self {
137 Node::Field(index) => *index += by,
138 Node::Embedded(e) => e.offset(by),
139 _ => {
140 for child in self.children_mut().unwrap_or_default() {
141 child.offset(by);
142 }
143 }
144 }
145 }
146
147 pub(crate) fn visit_fields(&self, f: &mut impl FnMut(usize, LeafPlace)) {
149 match self {
150 Node::Field(index) => f(*index, LeafPlace::Rendered),
151 Node::Embedded(e) => e.visit_fields(LeafPlace::Rendered, f),
152 _ => {
153 for child in self.children().unwrap_or_default() {
154 child.visit_fields(f);
155 }
156 }
157 }
158 }
159}
160
161pub(crate) async fn render_nodes<'a>(
163 cx: &'a Cx,
164 nodes: &[Node],
165 fields: &[Field],
166 source: &Source<'_>,
167) -> Result<BoxView<'a>> {
168 let mut views = Vec::with_capacity(nodes.len());
169 for node in nodes {
170 views.push(node.render(cx, fields, source).await?);
171 }
172 Ok(view! {
173 cx =>
174 for v in views {
175 (v)
176 }
177 }
178 .boxed())
179}
180
181pub(crate) fn walk_absent_groups(
184 nodes: &[Node],
185 fields: &[Field],
186 values: &HashMap<String, String>,
187 skip: &mut HashSet<String>,
188 errors: &mut FieldErrors,
189 inside_absent: bool,
190) {
191 let name = |index: usize| fields[index].name().to_string();
192 for node in nodes {
193 match node {
194 Node::Embedded(e) => {
195 let mut hidden = Vec::new();
196 e.hidden_fields(values, &mut hidden);
197 skip.extend(hidden.into_iter().map(name));
198 }
199 Node::Repeater(r) => {
200 let mut inner = Vec::new();
201 node.visit_fields(&mut |index, _| inner.push(name(index)));
202 let all_empty = inner
204 .iter()
205 .all(|n| values.get(n).map(|v| v.trim().is_empty()).unwrap_or(true));
206 if all_empty {
207 skip.extend(inner);
208 if r.required && !inside_absent && !errors.contains_key(&r.label) {
209 errors.add_required(&r.label, required_error(&r.label));
210 }
211 }
212 walk_absent_groups(
213 &r.children.nodes,
214 fields,
215 values,
216 skip,
217 errors,
218 inside_absent || all_empty,
219 );
220 }
221 _ => {
222 if let Some(children) = node.children() {
223 walk_absent_groups(children, fields, values, skip, errors, inside_absent);
224 }
225 }
226 }
227 }
228}
229
230pub trait IntoSchema {
232 fn into_schema(self) -> Schema;
233}
234
235impl IntoSchema for Schema {
236 fn into_schema(self) -> Schema {
237 self
238 }
239}
240
241impl IntoSchema for Field {
242 fn into_schema(self) -> Schema {
243 Schema {
244 nodes: vec![Node::Field(0)],
245 fields: vec![self],
246 }
247 }
248}
249
250macro_rules! container_nodes {
252 ($($ty:ident),+ $(,)?) => {
253 $(
254 impl IntoSchema for $ty {
255 fn into_schema(mut self) -> Schema {
256 let fields = std::mem::take(&mut self.children.fields);
257 Schema {
258 nodes: vec![Node::$ty(Box::new(self))],
259 fields,
260 }
261 }
262 }
263 )+
264 };
265}
266
267container_nodes!(Section, Group, Grid, Repeater);
268
269macro_rules! into_schema_tuples {
271 ($($T:ident => $v:ident),+ $(,)?) => {
272 impl<$($T),+> IntoSchema for ($($T,)+)
273 where
274 $($T: IntoSchema,)+
275 {
276 fn into_schema(self) -> Schema {
277 let ($($v,)+) = self;
278 let mut schema = Schema::empty();
279 $( schema.append($v.into_schema()); )+
280 schema
281 }
282 }
283 };
284}
285
286into_schema_tuples!(A => a, B => b);
287into_schema_tuples!(A => a, B => b, C => c);
288into_schema_tuples!(A => a, B => b, C => c, D => d);
289into_schema_tuples!(A => a, B => b, C => c, D => d, E => e);
290into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
291into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
292into_schema_tuples!(
293 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
294);
295
296#[cfg(test)]
297mod tests;