Skip to main content

tablo_core/schema/
tree.rs

1//! Composes field slots, layout containers, and embedded values into one tree.
2
3use std::collections::HashMap;
4
5use topcoat::{Result, context::Cx, view::*};
6
7use super::{
8    Schema,
9    condition::{Condition, watch},
10    embedded::Embedded,
11    fields::{Field, Placement},
12    layouts::{Grid, Group, Section},
13    lenses::FieldResolver,
14};
15use crate::form::{FieldError, FieldErrors, FormValue, FormValues};
16
17#[derive(Debug)]
18pub(crate) enum Node {
19    /// A field, by its index in the root schema's field list.
20    Field(usize),
21    Section(Box<Section>),
22    Group(Box<Group>),
23    Grid(Box<Grid>),
24    Embedded(Box<Embedded>),
25    /// An embedded value its schema has not bound yet: [`Schema::bind`] builds it through the app
26    /// schema and splices it in place.
27    Unbound(Unbound),
28}
29
30/// Builds an embedded value's node through the app schema it binds to.
31pub(crate) struct Unbound {
32    pub(crate) build: Box<dyn Fn(&FieldResolver) -> Schema + Send + Sync>,
33    /// The value's type, which the declaration error names.
34    pub(crate) value: &'static str,
35}
36
37impl std::fmt::Debug for Unbound {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        f.debug_tuple("Unbound").field(&self.value).finish()
40    }
41}
42
43/// Holds the values and errors a schema's controls render with.
44pub struct Source<'a> {
45    values: &'a FormValues,
46    errors: &'a FieldErrors,
47    /// The conditions each watched field's key drives.
48    watched: HashMap<String, Vec<Condition>>,
49    /// The prefix of the form's DOM ids and of its signals' keys; empty for a form alone on its
50    /// page.
51    scope: String,
52    /// Where a searchable choice fetches its options, when not the resource's own route.
53    options: Option<String>,
54    /// Whether the form edits a stored record.
55    editing: bool,
56}
57
58impl<'a> Source<'a> {
59    /// Renders controls hydrated with `values` and inline `errors`.
60    pub fn form(values: &'a FormValues, errors: &'a FieldErrors) -> Self {
61        Self {
62            values,
63            errors,
64            watched: HashMap::new(),
65            scope: String::new(),
66            options: None,
67            editing: false,
68        }
69    }
70
71    /// Renders the edit form of a stored record, where `disabled_on_edit` disables a control.
72    pub(crate) fn editing(mut self, editing: bool) -> Self {
73        self.editing = editing;
74        self
75    }
76
77    /// Renders a form sharing its page with another: its DOM ids and its signals' keys carry
78    /// `scope`, so neither form's labels, conditions or variants reach the other's.
79    pub(crate) fn scoped(mut self, scope: impl Into<String>) -> Self {
80        self.scope = scope.into();
81        self
82    }
83
84    /// Fetches a searchable choice's options from `url` rather than the resource's own route.
85    pub(crate) fn options_at(mut self, url: impl Into<String>) -> Self {
86        self.options = Some(url.into());
87        self
88    }
89
90    /// The prefix of the form's DOM ids and of its signals' keys.
91    pub(crate) fn scope(&self) -> &str {
92        &self.scope
93    }
94
95    /// Renders each watched field with the handlers its conditions follow.
96    pub(crate) fn watching(mut self, watched: HashMap<String, Vec<Condition>>) -> Self {
97        self.watched = watched;
98        self
99    }
100
101    /// Renders `view` guarded by `condition`, or as it is without one.
102    fn guard<'v>(
103        &self,
104        cx: &'v Cx,
105        condition: Option<&Condition>,
106        view: BoxView<'v>,
107    ) -> BoxView<'v> {
108        match condition {
109            Some(condition) => condition.guard(cx, &self.scope, self.values, view),
110            None => view,
111        }
112    }
113
114    pub(crate) fn value(&self, name: &str) -> Option<&FormValue> {
115        self.values.get(name)
116    }
117
118    /// The text `name` holds: a scalar control's value.
119    pub(crate) fn text(&self, name: &str) -> Option<&str> {
120        self.values.text(name)
121    }
122
123    /// The rows `name` holds: a repeater's, none when it holds none.
124    pub(crate) fn rows(&self, name: &str) -> &[FormValues] {
125        self.values.rows(name).unwrap_or_default()
126    }
127
128    /// The errors the controls render under.
129    pub(crate) fn errors(&self) -> &FieldErrors {
130        self.errors
131    }
132
133    /// Where a searchable choice fetches its options, when not the resource's own route.
134    pub(crate) fn options(&self) -> Option<&str> {
135        self.options.as_deref()
136    }
137
138    /// The DOM id of the control posting `name`.
139    pub(crate) fn id(&self, name: &str) -> String {
140        self.placement(None).id(name)
141    }
142
143    /// Where a field renders in this form, under its parent's value `parent`.
144    fn placement<'p>(&'p self, parent: Option<&'p str>) -> Placement<'p> {
145        Placement {
146            parent,
147            scope: &self.scope,
148            options: self.options.as_deref(),
149            editing: self.editing,
150        }
151    }
152
153    /// Returns the message the field `field` renders under its control.
154    pub(crate) fn error_for(&self, field: &Field) -> Option<&FieldError> {
155        self.errors.first(field.name())
156    }
157}
158
159impl Node {
160    /// Renders this node from `source`, reading field slots from `fields`.
161    pub(crate) async fn render<'a>(
162        &self,
163        cx: &'a Cx,
164        fields: &[Field],
165        source: &Source<'_>,
166    ) -> Result<BoxView<'a>> {
167        match self {
168            Node::Field(index) => {
169                let field = &fields[*index];
170                // A repeater's rows render from the keys and errors of each of their controls.
171                if let Some(repeater) = field.as_repeater() {
172                    let view = Box::pin(repeater.render(cx, field, source)).await?;
173                    return Ok(source.guard(cx, field.condition(), view));
174                }
175                let error = source.error_for(field);
176                let parent = field.parent_key().and_then(|key| source.text(key));
177                let mut view = Box::pin(field.render_under(
178                    cx,
179                    source.value(field.name()),
180                    error,
181                    source.placement(parent),
182                ))
183                .await?;
184                if let Some(conditions) = source.watched.get(field.name()) {
185                    view = watch(cx, field, conditions, &source.scope, source.values, view);
186                }
187                Ok(source.guard(cx, field.condition(), view))
188            }
189            Node::Section(s) => {
190                let view = Box::pin(s.render(cx, fields, source)).await?;
191                Ok(source.guard(cx, s.condition.as_ref(), view))
192            }
193            Node::Group(g) => {
194                let view = Box::pin(g.render(cx, fields, source)).await?;
195                Ok(source.guard(cx, g.condition.as_ref(), view))
196            }
197            Node::Grid(g) => {
198                let view = Box::pin(g.render(cx, fields, source)).await?;
199                Ok(source.guard(cx, g.condition.as_ref(), view))
200            }
201            Node::Embedded(e) => Box::pin(e.render(cx, fields, source)).await,
202            // `Schema::render` refuses an unbound schema before any node renders.
203            Node::Unbound(_) => Ok(().boxed()),
204        }
205    }
206
207    /// Returns the nodes a layout container holds, or `None` for a field slot and an embedded
208    /// value.
209    pub(crate) fn children(&self) -> Option<&[Node]> {
210        match self {
211            Node::Section(s) => Some(&s.children.nodes),
212            Node::Group(g) => Some(&g.children.nodes),
213            Node::Grid(g) => Some(&g.children.nodes),
214            Node::Field(_) | Node::Embedded(_) | Node::Unbound(_) => None,
215        }
216    }
217
218    fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
219        match self {
220            Node::Section(s) => Some(&mut s.children.nodes),
221            Node::Group(g) => Some(&mut g.children.nodes),
222            Node::Grid(g) => Some(&mut g.children.nodes),
223            Node::Field(_) | Node::Embedded(_) | Node::Unbound(_) => None,
224        }
225    }
226
227    /// Shifts every field slot under this node by `by`.
228    pub(crate) fn offset(&mut self, by: usize) {
229        match self {
230            Node::Field(index) => *index += by,
231            Node::Embedded(e) => e.offset(by),
232            _ => {
233                for child in self.children_mut().into_iter().flatten() {
234                    child.offset(by);
235                }
236            }
237        }
238    }
239}
240
241/// Builds every unbound node under `nodes` through `resolver` in place, appending its fields to
242/// `fields`.
243pub(crate) fn bind_nodes(nodes: &mut Vec<Node>, fields: &mut Vec<Field>, resolver: &FieldResolver) {
244    let mut bound = Vec::with_capacity(nodes.len());
245    for node in std::mem::take(nodes) {
246        let mut node = match node {
247            Node::Unbound(unbound) => {
248                let Schema {
249                    nodes: mut built,
250                    fields: built_fields,
251                    ..
252                } = (unbound.build)(resolver);
253                let offset = fields.len();
254                for node in &mut built {
255                    node.offset(offset);
256                }
257                fields.extend(built_fields);
258                bound.extend(built);
259                continue;
260            }
261            node => node,
262        };
263        if let Some(children) = node.children_mut() {
264            bind_nodes(children, fields, resolver);
265        }
266        bound.push(node);
267    }
268    *nodes = bound;
269}
270
271/// Collects the value types of the unbound nodes under `nodes`.
272pub(crate) fn unbound_values(nodes: &[Node], out: &mut Vec<&'static str>) {
273    for node in nodes {
274        match node {
275            Node::Unbound(unbound) => out.push(unbound.value),
276            node => unbound_values(node.children().unwrap_or_default(), out),
277        }
278    }
279}
280
281/// Renders `nodes` in order as one view.
282pub(crate) async fn render_nodes<'a>(
283    cx: &'a Cx,
284    nodes: &[Node],
285    fields: &[Field],
286    source: &Source<'_>,
287) -> Result<BoxView<'a>> {
288    let mut views = Vec::with_capacity(nodes.len());
289    for node in nodes {
290        views.push(node.render(cx, fields, source).await?);
291    }
292    Ok(view! {
293        cx =>
294        for v in views {
295            (v)
296        }
297    }
298    .boxed())
299}
300
301/// Converts a field, a layout block, a tuple of either, or a schema into a [`Schema`].
302///
303/// `F` is the record form whose controls the schema places, `()` for a page's or an action's
304/// schema: a control converts into the schema of its own form only.
305#[diagnostic::on_unimplemented(
306    message = "`{Self}` cannot be placed in a `Schema<{F}>`",
307    label = "not a control, layout block, schema, or tuple of 2 to 12 of them for this schema",
308    note = "a resource's form places its record form's own controls, from `controls()`; \
309            `Field::text` and the other constructors build a page's or an action's controls"
310)]
311pub trait IntoSchema<F = ()> {
312    fn into_schema(self) -> Schema<F>;
313}
314
315/// Moves a control or a schema to the form `G`: what `#[derive(RecordForm)]` hands its controls
316/// over with.
317#[doc(hidden)]
318pub trait Retype {
319    type As<G>;
320
321    fn retype<G>(self) -> Self::As<G>;
322}
323
324impl<F> IntoSchema<F> for Schema<F> {
325    fn into_schema(self) -> Schema<F> {
326        self
327    }
328}
329
330impl IntoSchema for Field {
331    fn into_schema(self) -> Schema {
332        Schema::from_parts(vec![Node::Field(0)], vec![self])
333    }
334}
335
336/// Generates the single-node [`IntoSchema`] impls for every layout container, holding a schema or
337/// nothing.
338macro_rules! container_nodes {
339    ($($ty:ident),+ $(,)?) => {
340        $(
341            impl<F> IntoSchema<F> for $ty<Schema<F>> {
342                fn into_schema(self) -> Schema<F> {
343                    let mut fields = Vec::new();
344                    let block = self.map(|mut children| {
345                        fields = std::mem::take(&mut children.fields);
346                        children.retype()
347                    });
348                    Schema::from_parts(vec![Node::$ty(Box::new(block))], fields)
349                }
350            }
351
352            impl<F> IntoSchema<F> for $ty<()> {
353                fn into_schema(self) -> Schema<F> {
354                    self.holding(Schema::<F>::default()).into_schema()
355                }
356            }
357        )+
358    };
359}
360
361container_nodes!(Section, Group, Grid);
362
363/// Generates the tuple impls of [`IntoSchema`] from one list per arity.
364macro_rules! into_schema_tuples {
365    ($($T:ident => $v:ident),+ $(,)?) => {
366        impl<Form, $($T),+> IntoSchema<Form> for ($($T,)+)
367        where
368            $($T: IntoSchema<Form>,)+
369        {
370            fn into_schema(self) -> Schema<Form> {
371                let ($($v,)+) = self;
372                let mut schema = Schema::default();
373                $( schema.append($v.into_schema()); )+
374                schema
375            }
376        }
377    };
378}
379
380into_schema_tuples!(A => a, B => b);
381into_schema_tuples!(A => a, B => b, C => c);
382into_schema_tuples!(A => a, B => b, C => c, D => d);
383into_schema_tuples!(A => a, B => b, C => c, D => d, E => e);
384into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
385into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
386into_schema_tuples!(
387    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
388);
389into_schema_tuples!(
390    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i
391);
392into_schema_tuples!(
393    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j
394);
395into_schema_tuples!(
396    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k
397);
398into_schema_tuples!(
399    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l
400);