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,
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) table_name: String,
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) expressions: IndexMap<String, ExpressionFn<T, E>>,
33    pub(super) pagination: Option<Pagination>,
34    pub(super) title_field: Option<String>,
35    pub(super) title_fields: Vec<String>,
36    pub(super) id_field: Option<String>,
37}
38
39impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
40    /// Create a new Table with the given table name and data source
41    pub fn new(table_name: impl Into<String>, data_source: T) -> Self {
42        Self {
43            data_source,
44            _phantom: PhantomData,
45            table_name: table_name.into(),
46            columns: IndexMap::new(),
47            conditions: IndexMap::new(),
48            next_condition_id: 1,
49            order_by: IndexMap::new(),
50            next_order_id: 1,
51            refs: None,
52            expressions: IndexMap::new(),
53            pagination: None,
54            title_field: None,
55            title_fields: Vec::new(),
56            id_field: None,
57        }
58    }
59
60    /// Convert this table to use a different entity type
61    pub fn into_entity<E2: Entity<T::Value>>(self) -> Table<T, E2> {
62        Table {
63            data_source: self.data_source,
64            _phantom: PhantomData,
65            table_name: self.table_name,
66            columns: self.columns,
67            conditions: self.conditions,
68            next_condition_id: self.next_condition_id,
69            order_by: self.order_by,
70            next_order_id: self.next_order_id,
71            refs: self.refs,
72            expressions: IndexMap::new(),
73            pagination: self.pagination,
74            title_field: self.title_field,
75            title_fields: self.title_fields,
76            id_field: self.id_field,
77        }
78    }
79
80    /// Snapshot the table's relations as Vista references (name, target type,
81    /// cardinality, foreign key). Driver factories fold this into
82    /// `VistaMetadata` so the erased `Vista` carries enough to drive nested
83    /// insert and relation traversal.
84    pub fn vista_references(&self) -> Vec<vantage_vista::Reference> {
85        self.refs
86            .as_ref()
87            .map(|refs| {
88                refs.iter()
89                    .map(|(name, r)| {
90                        vantage_vista::Reference::new(
91                            name.clone(),
92                            r.target_type_name().to_string(),
93                            r.cardinality(),
94                            r.foreign_key().to_string(),
95                        )
96                    })
97                    .collect()
98            })
99            .unwrap_or_default()
100    }
101
102    /// Use a callback with a builder pattern for configuration
103    pub fn with<F>(mut self, func: F) -> Self
104    where
105        F: FnOnce(&mut Self),
106    {
107        func(&mut self);
108        self
109    }
110
111    /// Get the table name
112    pub fn table_name(&self) -> &str {
113        &self.table_name
114    }
115
116    /// Override the table name. Used by REST API drivers to swap a
117    /// canonical resource path for a per-reference URI template at
118    /// traversal time.
119    pub fn set_table_name(&mut self, name: impl Into<String>) {
120        self.table_name = name.into();
121    }
122
123    /// Get the underlying data source
124    pub fn data_source(&self) -> &T {
125        &self.data_source
126    }
127
128    /// Get mutable access to conditions (pub(crate) for TableLike impl)
129    pub(crate) fn conditions_mut(&mut self) -> &mut IndexMap<i64, T::Condition> {
130        &mut self.conditions
131    }
132
133    /// Get mutable access to next_condition_id (pub(crate) for TableLike impl)
134    pub(crate) fn next_condition_id_mut(&mut self) -> &mut i64 {
135        &mut self.next_condition_id
136    }
137
138    /// Get the title field column if set
139    pub fn title_field(&self) -> Option<&T::Column<T::AnyType>> {
140        self.title_field
141            .as_ref()
142            .and_then(|name| self.columns.get(name))
143    }
144
145    /// Names of columns marked as display titles (set via
146    /// [`Self::with_title_column_of`]). These show alongside the id in
147    /// list views and on the leading lines of single-record displays.
148    pub fn title_fields(&self) -> &[String] {
149        &self.title_fields
150    }
151
152    /// Get the id field column if set
153    pub fn id_field(&self) -> Option<&T::Column<T::AnyType>> {
154        self.id_field
155            .as_ref()
156            .and_then(|name| self.columns.get(name))
157    }
158
159    /// Mark an already-added column as the id field.
160    ///
161    /// Use this when the id column has been added via [`Self::add_column`]
162    /// (so its type and aliases were chosen explicitly) and you only need
163    /// to flag it. [`Self::with_id_column`] is the typed shortcut that
164    /// creates the column for you.
165    pub fn set_id_field(&mut self, name: impl Into<String>) {
166        self.id_field = Some(name.into());
167    }
168
169    /// Mark an already-added column as a display title.
170    ///
171    /// Companion to [`Self::set_id_field`] for spec-driven construction.
172    pub fn add_title_field(&mut self, name: impl Into<String>) {
173        let name = name.into();
174        if !self.title_fields.contains(&name) {
175            self.title_fields.push(name.clone());
176        }
177        if self.title_field.is_none() {
178            self.title_field = Some(name);
179        }
180    }
181
182    /// Get the current pagination configuration, if set
183    pub fn pagination(&self) -> Option<&Pagination> {
184        self.pagination.as_ref()
185    }
186}
187
188impl<T: TableSource, E: Entity<T::Value>> std::ops::Index<&str> for Table<T, E> {
189    type Output = T::Column<T::AnyType>;
190
191    fn index(&self, index: &str) -> &Self::Output {
192        &self.columns[index]
193    }
194}
195
196impl<T: TableSource, E: Entity<T::Value>> std::fmt::Debug for Table<T, E> {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        f.debug_struct("Table")
199            .field("table_name", &self.table_name)
200            .field("columns", &self.columns.keys().collect::<Vec<_>>())
201            .field("conditions_count", &self.conditions.len())
202            .field(
203                "refs_count",
204                &self.refs.as_ref().map(|r| r.len()).unwrap_or(0),
205            )
206            .field("expressions_count", &self.expressions.len())
207            .finish()
208    }
209}