Skip to main content

tablo_core/column/
embedded.rs

1//! [`EmbeddedColumn`]: an embedded value read off the record, one leaf at a time.
2
3use std::sync::Arc;
4
5use derive_where::derive_where;
6use topcoat::{context::Cx, view::*};
7
8use super::{Column, ColumnBase, ColumnWidth};
9use crate::{
10    Lens,
11    schema::{EmbeddedForm, Schema},
12    values::FormValues,
13};
14
15/// A column of an [`EmbeddedForm`] value.
16///
17/// A detail page shows each of the value's leaves under its own label; an enum shows its variant's
18/// name and that variant's leaves only. A table cell lists the same leaves as `label: value`.
19///
20/// ```rust
21/// # #[derive(Debug, Clone, toasty::Embed, tablo_core::EmbeddedForm)]
22/// # struct Seo { title: String, description: String }
23/// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
24/// # struct Post { #[key] #[auto] id: uuid::Uuid, seo: Seo }
25/// tablo_core::EmbeddedColumn::new(tablo_core::lens!(Post.seo));
26/// ```
27#[derive_where(Clone, Debug)]
28pub struct EmbeddedColumn<M, T> {
29    lens: Lens<M, T>,
30    base: ColumnBase,
31    /// The value's schema.
32    schema: Arc<Schema>,
33}
34
35impl<M, T> EmbeddedColumn<M, T>
36where
37    M: toasty::schema::Model,
38    T: EmbeddedForm,
39{
40    /// Bind the column to the embedded value `lens` reads.
41    pub fn new(lens: Lens<M, T>) -> Self {
42        Self {
43            base: ColumnBase::new(lens.path(), ColumnWidth::Grow),
44            schema: Arc::new(T::build_schema(lens.path().clone())),
45            lens,
46        }
47    }
48
49    base_builders!();
50
51    /// The value's schema and its leaves as the form spells them.
52    fn spelled(&self, row: &M) -> (&Schema, FormValues) {
53        let mut values = FormValues::new();
54        self.lens
55            .read(row)
56            .write_node(self.schema.embedded_root(), &mut values);
57        (&self.schema, values)
58    }
59}
60
61impl<M, T> Column<M> for EmbeddedColumn<M, T>
62where
63    M: toasty::schema::Model + Send + Sync + 'static,
64    T: EmbeddedForm + Send + Sync + 'static,
65{
66    base_column_methods!();
67
68    fn text(&self, cx: &Cx, row: &M) -> String {
69        let (schema, values) = self.spelled(row);
70        schema
71            .embedded_root()
72            .shown(&schema.fields, &values)
73            .into_iter()
74            .filter_map(|index| {
75                let field = &schema.fields[index];
76                let value = values.text(field.name()).unwrap_or_default();
77                let read = field.read(cx, value)?;
78                Some(format!("{}: {read}", field.label().localize(cx)))
79            })
80            .collect::<Vec<_>>()
81            .join(", ")
82    }
83
84    fn entry<'a>(&self, cx: &'a Cx, row: &M) -> BoxView<'a> {
85        let (schema, values) = self.spelled(row);
86        schema.embedded_root().display(cx, &schema.fields, &values)
87    }
88}
89
90impl<M, T> crate::column::ColumnInternals<M> for EmbeddedColumn<M, T>
91where
92    M: toasty::schema::Model + Send + Sync + 'static,
93    T: EmbeddedForm + Send + Sync + 'static,
94{
95    base_internals!();
96
97    fn misdeclared(&self) -> Option<crate::DeclarationErrorKind> {
98        if let Some(error) = self.base.binding.misdeclared() {
99            return Some(error);
100        }
101        self.schema.declaration_errors().into_iter().next()
102    }
103}