Skip to main content

tablo_core/table/column/
model.rs

1//! [`ModelColumns`]: the table and detail page a model reads as by default.
2
3use toasty::{schema::Model, stmt::IntoExpr};
4
5use crate::{Detail, Table};
6
7/// The table and detail page a model reads as when its resource declares none: what a
8/// [`ResourceDef`](crate::ResourceDef) without a [`table`](crate::ResourceDef::table) lists and
9/// without a [`detail`](crate::ResourceDef::detail) shows.
10///
11/// Derive it with [`ModelColumns`](derive@crate::ModelColumns), which gives a column to each field
12/// a person reads, whether a form or Toasty writes it. An impl that keeps the defaults here lists
13/// no column and turns the detail page off, for a resource that declares both.
14#[diagnostic::on_unimplemented(
15    message = "`{Self}` declares no default columns",
16    label = "a resource's model implements `ModelColumns`",
17    note = "add `#[derive(tablo::ModelColumns)]` to `{Self}`, or `impl tablo::ModelColumns for \
18            {Self} {{}}` when its resources declare their own table and detail page"
19)]
20pub trait ModelColumns: Model + IntoExpr<Self> + Sized {
21    /// The default table, which lists no column here.
22    fn table() -> Table<Self> {
23        Table::new(())
24    }
25
26    /// The default detail page, which is off here.
27    fn detail() -> Detail<Self> {
28        Detail::empty()
29    }
30}
31
32/// How the [`ModelColumns`](derive@crate::ModelColumns) derive picks a field's column from its
33/// type, a choice no trait bound can make: each `Pick*` trait is implemented on
34/// [`ModelField`](pick::ModelField) behind one more reference than the next, so method lookup on
35/// `&&&&&&ModelField` takes the first whose bounds the field's type meets.
36#[doc(hidden)]
37pub mod pick {
38    use toasty::{
39        schema::{Field, Model},
40        stmt::List,
41    };
42
43    use crate::{
44        BooleanColumn, Detail, EmbeddedColumn, Lens, RepeaterColumn, Table, TextColumn,
45        form::FormScalar,
46        schema::{EmbeddedForm, RepeaterItem},
47    };
48
49    /// One model field, as the derive hands it to the `Pick*` traits.
50    pub struct ModelField<M, T, P = T>(pub Lens<M, T, P>);
51
52    /// The type a field of type `T` names its path by: `T`, except `List<T>` for a `Vec<T>`.
53    pub type PathOf<T> = <T as Field>::ExprTarget;
54
55    /// A `bool`: a sortable yes or no.
56    pub trait PickToggle<M> {
57        fn table(&self, table: Table<M>) -> Table<M>;
58        fn detail(&self, detail: Detail<M>) -> Detail<M>;
59    }
60
61    impl<M> PickToggle<M> for &&&&&ModelField<M, bool>
62    where
63        M: Model + Send + Sync + 'static,
64    {
65        fn table(&self, table: Table<M>) -> Table<M> {
66            table.column(BooleanColumn::new(self.0.clone()).sortable())
67        }
68
69        fn detail(&self, detail: Detail<M>) -> Detail<M> {
70            detail.column(BooleanColumn::new(self.0.clone()))
71        }
72    }
73
74    /// A `String` or `Option<String>`: a sortable, searchable text.
75    pub trait PickString<M> {
76        fn table(&self, table: Table<M>) -> Table<M>;
77        fn detail(&self, detail: Detail<M>) -> Detail<M>;
78    }
79
80    impl<M, T> PickString<M> for &&&&ModelField<M, T>
81    where
82        M: Model + Send + Sync + 'static,
83        T: FormScalar + Field<Inner = String> + Send + Sync + 'static,
84    {
85        fn table(&self, table: Table<M>) -> Table<M> {
86            table.column(TextColumn::new(self.0.clone()).sortable().searchable())
87        }
88
89        fn detail(&self, detail: Detail<M>) -> Detail<M> {
90            detail.column(TextColumn::new(self.0.clone()))
91        }
92    }
93
94    /// Any other [`FormScalar`]: a sortable text, an `Options` enum's by its label.
95    pub trait PickScalar<M> {
96        fn table(&self, table: Table<M>) -> Table<M>;
97        fn detail(&self, detail: Detail<M>) -> Detail<M>;
98    }
99
100    impl<M, T> PickScalar<M> for &&&ModelField<M, T>
101    where
102        M: Model + Send + Sync + 'static,
103        T: FormScalar + Send + Sync + 'static,
104    {
105        fn table(&self, table: Table<M>) -> Table<M> {
106            table.column(TextColumn::new(self.0.clone()).sortable())
107        }
108
109        fn detail(&self, detail: Detail<M>) -> Detail<M> {
110            detail.column(TextColumn::new(self.0.clone()))
111        }
112    }
113
114    /// An [`EmbeddedForm`] value: its leaves on the detail page, no table column.
115    pub trait PickEmbedded<M> {
116        fn table(&self, table: Table<M>) -> Table<M>;
117        fn detail(&self, detail: Detail<M>) -> Detail<M>;
118    }
119
120    impl<M, T> PickEmbedded<M> for &&ModelField<M, T>
121    where
122        M: Model + Send + Sync + 'static,
123        T: EmbeddedForm + Send + Sync + 'static,
124    {
125        fn table(&self, table: Table<M>) -> Table<M> {
126            table
127        }
128
129        fn detail(&self, detail: Detail<M>) -> Detail<M> {
130            detail.column(EmbeddedColumn::new(self.0.clone()))
131        }
132    }
133
134    /// A list of [`RepeaterItem`] values: its items on the detail page, no table column.
135    pub trait PickRepeater<M> {
136        fn table(&self, table: Table<M>) -> Table<M>;
137        fn detail(&self, detail: Detail<M>) -> Detail<M>;
138    }
139
140    impl<M, T> PickRepeater<M> for &ModelField<M, Vec<T>, List<T>>
141    where
142        M: Model + Send + Sync + 'static,
143        T: RepeaterItem + Send + Sync + 'static,
144    {
145        fn table(&self, table: Table<M>) -> Table<M> {
146            table
147        }
148
149        fn detail(&self, detail: Detail<M>) -> Detail<M> {
150            detail.column(RepeaterColumn::new(self.0.clone()))
151        }
152    }
153
154    /// Any other type, which no column reads: nowhere.
155    pub trait PickNone<M> {
156        fn table(&self, table: Table<M>) -> Table<M>;
157        fn detail(&self, detail: Detail<M>) -> Detail<M>;
158    }
159
160    impl<M, T, P> PickNone<M> for ModelField<M, T, P> {
161        fn table(&self, table: Table<M>) -> Table<M> {
162            table
163        }
164
165        fn detail(&self, detail: Detail<M>) -> Detail<M> {
166            detail
167        }
168    }
169}