Skip to main content

vantage_table/table/
base.rs

1use std::marker::PhantomData;
2use std::sync::Arc;
3
4use indexmap::IndexMap;
5use vantage_expressions::Expression;
6use vantage_types::Entity;
7
8use crate::{
9    pagination::Pagination, references::Reference, sorting::SortDirection,
10    traits::table_source::TableSource, traits::table_source_spec::TableSourceSpec,
11};
12
13/// Type alias for expression closures stored on Table.
14pub type ExpressionFn<T, E> =
15    Arc<dyn Fn(&Table<T, E>) -> Expression<<T as TableSource>::Value> + Send + Sync>;
16
17#[derive(Clone)]
18pub struct Table<T, E>
19where
20    T: TableSource,
21    E: Entity<T::Value>,
22{
23    pub(super) data_source: T,
24    pub(super) _phantom: PhantomData<E>,
25    pub(super) source: T::Source,
26    pub(super) columns: IndexMap<String, T::Column<T::AnyType>>,
27    pub(super) conditions: IndexMap<i64, T::Condition>,
28    pub(super) next_condition_id: i64,
29    pub(super) order_by: IndexMap<i64, (T::Condition, SortDirection)>,
30    pub(super) next_order_id: i64,
31    pub(super) refs: Option<IndexMap<String, Arc<dyn Reference>>>,
32    pub(super) contained: Vec<crate::references::ContainedRelation<T>>,
33    pub(super) expressions: IndexMap<String, ExpressionFn<T, E>>,
34    pub(super) pagination: Option<Pagination>,
35    pub(super) title_field: Option<String>,
36    pub(super) title_fields: Vec<String>,
37    pub(super) id_field: Option<String>,
38}
39
40impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
41    /// Create a new Table with the given table name and data source
42    pub fn new(table_name: impl Into<String>, data_source: T) -> Self {
43        Self {
44            data_source,
45            _phantom: PhantomData,
46            source: T::Source::from_name(table_name.into()),
47            columns: IndexMap::new(),
48            conditions: IndexMap::new(),
49            next_condition_id: 1,
50            order_by: IndexMap::new(),
51            next_order_id: 1,
52            refs: None,
53            contained: Vec::new(),
54            expressions: IndexMap::new(),
55            pagination: None,
56            title_field: None,
57            title_fields: Vec::new(),
58            id_field: None,
59        }
60    }
61
62    /// Convert this table to use a different entity type
63    pub fn into_entity<E2: Entity<T::Value>>(self) -> Table<T, E2> {
64        Table {
65            data_source: self.data_source,
66            _phantom: PhantomData,
67            source: self.source,
68            columns: self.columns,
69            conditions: self.conditions,
70            next_condition_id: self.next_condition_id,
71            order_by: self.order_by,
72            next_order_id: self.next_order_id,
73            refs: self.refs,
74            contained: self.contained,
75            expressions: IndexMap::new(),
76            pagination: self.pagination,
77            title_field: self.title_field,
78            title_fields: self.title_fields,
79            id_field: self.id_field,
80        }
81    }
82
83    /// Snapshot the table's relations as Vista references (name, target type,
84    /// cardinality, foreign key). Driver factories fold this into
85    /// `VistaMetadata` so the erased `Vista` carries enough to drive nested
86    /// insert and relation traversal.
87    pub fn vista_references(&self) -> Vec<vantage_vista::Reference> {
88        self.refs
89            .as_ref()
90            .map(|refs| {
91                refs.iter()
92                    .map(|(name, r)| {
93                        vantage_vista::Reference::new(
94                            name.clone(),
95                            r.target_type_name().to_string(),
96                            r.cardinality(),
97                            r.foreign_key().to_string(),
98                        )
99                    })
100                    .collect()
101            })
102            .unwrap_or_default()
103    }
104
105    /// Shape-only specs (name, host, kind, id) for the contained relations
106    /// declared on this table, for driver factories to fold into
107    /// `VistaMetadata`. Columns are derived at traversal from each relation's
108    /// `build_target` closure.
109    pub fn vista_contained(&self) -> Vec<vantage_vista::ContainedSpec> {
110        self.contained.iter().map(|c| c.spec()).collect()
111    }
112
113    /// Look up a contained relation by name (for the driver's traversal).
114    pub fn contained_relation(
115        &self,
116        name: &str,
117    ) -> Option<&crate::references::ContainedRelation<T>> {
118        self.contained.iter().find(|c| c.name() == name)
119    }
120
121    /// Use a callback with a builder pattern for configuration
122    pub fn with<F>(mut self, func: F) -> Self
123    where
124        F: FnOnce(&mut Self),
125    {
126        func(&mut self);
127        self
128    }
129
130    /// Get the table name.
131    ///
132    /// For a query-sourced table this is its FROM alias.
133    pub fn table_name(&self) -> &str {
134        self.source.name()
135    }
136
137    /// The table's source (a name, or a query used as a derived source).
138    pub fn source(&self) -> &T::Source {
139        &self.source
140    }
141
142    /// Override the table name. Used by REST API drivers to swap a
143    /// canonical resource path for a per-reference URI template at
144    /// traversal time.
145    ///
146    /// This replaces the source with a name-based one, so it must not be
147    /// called on a query-sourced (derived) table.
148    pub fn set_table_name(&mut self, name: impl Into<String>) {
149        self.source = T::Source::from_name(name.into());
150    }
151
152    /// Get the underlying data source
153    pub fn data_source(&self) -> &T {
154        &self.data_source
155    }
156
157    /// Get mutable access to conditions (pub(crate) for TableLike impl)
158    pub(crate) fn conditions_mut(&mut self) -> &mut IndexMap<i64, T::Condition> {
159        &mut self.conditions
160    }
161
162    /// Get mutable access to next_condition_id (pub(crate) for TableLike impl)
163    pub(crate) fn next_condition_id_mut(&mut self) -> &mut i64 {
164        &mut self.next_condition_id
165    }
166
167    /// Get the title field column if set
168    pub fn title_field(&self) -> Option<&T::Column<T::AnyType>> {
169        self.title_field
170            .as_ref()
171            .and_then(|name| self.columns.get(name))
172    }
173
174    /// Names of columns marked as display titles (set via
175    /// [`Self::with_title_column_of`]). These show alongside the id in
176    /// list views and on the leading lines of single-record displays.
177    pub fn title_fields(&self) -> &[String] {
178        &self.title_fields
179    }
180
181    /// Get the id field column if set
182    pub fn id_field(&self) -> Option<&T::Column<T::AnyType>> {
183        self.id_field
184            .as_ref()
185            .and_then(|name| self.columns.get(name))
186    }
187
188    /// Mark an already-added column as the id field.
189    ///
190    /// Use this when the id column has been added via [`Self::add_column`]
191    /// (so its type and aliases were chosen explicitly) and you only need
192    /// to flag it. [`Self::with_id_column`] is the typed shortcut that
193    /// creates the column for you.
194    pub fn set_id_field(&mut self, name: impl Into<String>) {
195        self.id_field = Some(name.into());
196    }
197
198    /// Mark an already-added column as a display title.
199    ///
200    /// Companion to [`Self::set_id_field`] for spec-driven construction.
201    pub fn add_title_field(&mut self, name: impl Into<String>) {
202        let name = name.into();
203        if !self.title_fields.contains(&name) {
204            self.title_fields.push(name.clone());
205        }
206        if self.title_field.is_none() {
207            self.title_field = Some(name);
208        }
209    }
210
211    /// Get the current pagination configuration, if set
212    pub fn pagination(&self) -> Option<&Pagination> {
213        self.pagination.as_ref()
214    }
215}
216
217impl<T: TableSource, E: Entity<T::Value>> std::ops::Index<&str> for Table<T, E> {
218    type Output = T::Column<T::AnyType>;
219
220    fn index(&self, index: &str) -> &Self::Output {
221        &self.columns[index]
222    }
223}
224
225impl<T: TableSource, E: Entity<T::Value>> std::fmt::Debug for Table<T, E> {
226    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227        f.debug_struct("Table")
228            .field("table_name", &self.table_name())
229            .field("columns", &self.columns.keys().collect::<Vec<_>>())
230            .field("conditions_count", &self.conditions.len())
231            .field(
232                "refs_count",
233                &self.refs.as_ref().map(|r| r.len()).unwrap_or(0),
234            )
235            .field("expressions_count", &self.expressions.len())
236            .finish()
237    }
238}