vantage_table/table/impls/
spec.rs1use std::sync::Arc;
6
7use indexmap::IndexMap;
8use vantage_core::{Result, VantageError, error};
9use vantage_expressions::{Expressive, SelectableDataSource};
10use vantage_types::{EmptyEntity, Entity};
11use vantage_vista::{
12 ColumnFlag, ColumnSpec, JoinKey, ReferenceKind, ReferenceSugar, SpecResolver, VistaSpec,
13};
14
15use crate::{source::SelectSource, table::Table, traits::table_source::TableSource};
16
17type SpecTableBuilder<S, T> =
19 fn(&S, T, Option<Arc<dyn SpecResolver<S>>>) -> Result<Table<T, EmptyEntity>>;
20
21impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
22 pub fn add_spec_column<C>(
27 &mut self,
28 name: &str,
29 spec: &ColumnSpec<C>,
30 build: impl FnOnce(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
31 ) -> Result<bool> {
32 if self.add_lazy_spec_column(spec, name)? {
33 return Ok(false);
34 }
35 if !self.columns.contains_key(name) {
36 self.add_column(build(name, spec)?);
37 }
38 if spec.has_flag(ColumnFlag::Title) {
39 self.add_title_field(name);
40 }
41 Ok(true)
42 }
43
44 pub fn add_spec_columns<C>(
46 &mut self,
47 columns: &IndexMap<String, ColumnSpec<C>>,
48 build: impl Fn(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
49 ) -> Result<()> {
50 for (name, spec) in columns {
51 self.add_spec_column(name, spec, &build)?;
52 }
53 Ok(())
54 }
55
56 pub fn set_spec_id_field(&mut self, id: &str) -> Result<()> {
59 if !self.columns.contains_key(id) {
60 return Err(error!("id column not present in spec.columns", id = id));
61 }
62 self.set_id_field(id);
63 Ok(())
64 }
65}
66
67impl<T: TableSource + 'static, E: Entity<T::Value> + 'static> Table<T, E>
68where
69 T::Value: Into<ciborium::Value> + From<ciborium::Value>,
70 T::Id: std::fmt::Display + From<String>,
71{
72 pub fn with_spec_references<X, C, R, S: 'static>(
82 mut self,
83 spec: &VistaSpec<X, C, R>,
84 resolver: Option<Arc<dyn SpecResolver<S>>>,
85 build: SpecTableBuilder<S, T>,
86 ) -> Result<Self> {
87 for (relation, reference) in &spec.references {
88 let foreign_key = reference.foreign_key(relation);
89 self = self.with_spec_join(
90 relation,
91 &reference.table,
92 reference.kind,
93 &foreign_key,
94 resolver.clone(),
95 build,
96 )?;
97 }
98 for (column, sugar) in column_references(spec) {
99 let (target, kind, foreign_key) = match sugar {
100 ReferenceSugar::Sugar(target) => (target, ReferenceKind::HasOne, column.clone()),
101 ReferenceSugar::Full(r) => (&r.table, r.kind, r.foreign_key(column)),
102 _ => return Err(unknown_sugar(column)),
103 };
104 self =
105 self.with_spec_join(column, target, kind, &foreign_key, resolver.clone(), build)?;
106 }
107 Ok(self)
108 }
109
110 fn with_spec_join<S: 'static>(
111 self,
112 relation: &str,
113 target: &str,
114 kind: ReferenceKind,
115 foreign_key: &str,
116 resolver: Option<Arc<dyn SpecResolver<S>>>,
117 build: SpecTableBuilder<S, T>,
118 ) -> Result<Self> {
119 let target = target.to_string();
120 let build_target = move |source: T| -> Table<T, EmptyEntity> {
121 if let Some(r) = &resolver
122 && let Some(spec) = r.resolve(&target)
123 && let Ok(table) = build(&spec, source.clone(), Some(r.clone()))
124 {
125 return table;
126 }
127 Table::new(target.clone(), source)
128 };
129 match kind {
130 ReferenceKind::HasOne => Ok(self.with_one(relation, foreign_key, build_target)),
131 ReferenceKind::HasMany => Ok(self.with_many(relation, foreign_key, build_target)),
132 _ => Err(error!(
133 "references: entries must be has_one or has_many",
134 relation = relation
135 )),
136 }
137 }
138}
139
140pub fn spec_references<X, C, R>(
147 spec: &VistaSpec<X, C, R>,
148 source_id: &str,
149) -> Result<Vec<vantage_vista::Reference>> {
150 let mut out = Vec::with_capacity(spec.references.len());
151 for (relation, reference) in &spec.references {
152 out.push(reference.to_reference(relation, source_id)?);
153 }
154 for (column, sugar) in column_references(spec) {
155 out.push(match sugar {
156 ReferenceSugar::Sugar(target) => {
157 vantage_vista::Reference::has_one(column, target, JoinKey::to_target_id(column))
158 }
159 ReferenceSugar::Full(r) => r.to_reference(column, source_id)?,
160 _ => return Err(unknown_sugar(column)),
161 });
162 }
163 Ok(out)
164}
165
166fn unknown_sugar(column: &str) -> VantageError {
167 error!(
168 "column references: takes a table name or a full reference",
169 column = column
170 )
171}
172
173fn column_references<X, C, R>(
176 spec: &VistaSpec<X, C, R>,
177) -> impl Iterator<Item = (&String, &ReferenceSugar)> {
178 spec.columns.iter().filter_map(|(column, col)| {
179 let sugar = col.references.as_ref()?;
180 (!spec.references.contains_key(column)).then_some((column, sugar))
181 })
182}
183
184impl<T, E, V, Cond, S> Table<T, E>
186where
187 T: SelectableDataSource<V, Cond, Select = S>
188 + TableSource<Value = V, Condition = Cond, Source = SelectSource<S>>,
189 V: Clone + Send + Sync + 'static + From<String>,
190 Cond: Clone + Send + Sync + 'static,
191 S: Expressive<V> + Clone,
192 E: Entity<V>,
193{
194 pub fn derive_from_spec<E2: Entity<V> + 'static, X, C, R>(
199 base: &Table<T, E2>,
200 spec: &VistaSpec<X, C, R>,
201 select: S,
202 inherit_columns: &[String],
203 inherit_relations: &[String],
204 build_column: impl Fn(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
205 ) -> Result<Self>
206 where
207 T: 'static,
208 E: 'static,
209 {
210 let columns: Vec<&str> = inherit_columns.iter().map(String::as_str).collect();
211 let relations: Vec<&str> = inherit_relations.iter().map(String::as_str).collect();
212 let mut table = Self::derive_from(
213 base,
214 spec.name.clone(),
215 move |_| select,
216 &columns,
217 &relations,
218 );
219 table.add_spec_columns(&spec.columns, &build_column)?;
220 if let Some(id) = &spec.id_column {
221 table.set_id_field(id);
222 }
223 table.with_contained_specs(&spec.contained, build_column)
224 }
225}