vantage_table/table/
inject.rs1use std::fmt;
11use std::sync::Arc;
12
13use vantage_core::{Result, error};
14use vantage_types::EmptyEntity;
15use vantage_vista::SpecResolver;
16
17use crate::table::Table;
18use crate::traits::table_source::TableSource;
19
20#[derive(Clone, Debug, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum BuildKind {
24 Root,
27 Reference { from: String, relation: String },
30 Base { derived: String },
35}
36
37#[non_exhaustive]
40pub struct InjectCx<'a, T: TableSource> {
41 pub key: &'a str,
42 pub kind: BuildKind,
43 pub build: &'a dyn Fn(&str) -> Result<Table<T, EmptyEntity>>,
48}
49
50pub type TableInjector<T> =
52 Arc<dyn Fn(&InjectCx<'_, T>, &mut Table<T, EmptyEntity>) -> Result<()> + Send + Sync>;
53
54#[derive(Clone, Debug, Default, PartialEq, Eq)]
57pub struct BuildTrail {
58 keys: Vec<String>,
59}
60
61impl BuildTrail {
62 pub fn enter(&self, key: &str) -> Result<BuildTrail> {
65 if self.keys.iter().any(|k| k == key) {
66 let path = self
67 .keys
68 .iter()
69 .map(String::as_str)
70 .chain([key])
71 .collect::<Vec<_>>()
72 .join(" -> ");
73 return Err(error!("table build cycle", path = path));
74 }
75 let mut keys = self.keys.clone();
76 keys.push(key.to_string());
77 Ok(BuildTrail { keys })
78 }
79
80 pub fn keys(&self) -> &[String] {
81 &self.keys
82 }
83}
84
85pub type SpecTableBuilder<S, T> = fn(&S, T, &BuildEnv<S, T>) -> Result<Table<T, EmptyEntity>>;
88
89pub struct BuildEnv<S, T: TableSource> {
93 pub resolver: Option<Arc<dyn SpecResolver<S>>>,
94 pub injector: Option<TableInjector<T>>,
95 pub trail: BuildTrail,
96 pub kind: BuildKind,
97}
98
99impl<S, T: TableSource> Clone for BuildEnv<S, T> {
100 fn clone(&self) -> Self {
101 Self {
102 resolver: self.resolver.clone(),
103 injector: self.injector.clone(),
104 trail: self.trail.clone(),
105 kind: self.kind.clone(),
106 }
107 }
108}
109
110impl<S, T: TableSource> fmt::Debug for BuildEnv<S, T> {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.debug_struct("BuildEnv")
113 .field("resolver", &self.resolver.is_some())
114 .field("injector", &self.injector.is_some())
115 .field("trail", &self.trail)
116 .field("kind", &self.kind)
117 .finish()
118 }
119}
120
121impl<S: 'static, T: TableSource> BuildEnv<S, T> {
122 pub fn root(
124 key: &str,
125 resolver: Option<Arc<dyn SpecResolver<S>>>,
126 injector: Option<TableInjector<T>>,
127 ) -> Self {
128 Self {
129 resolver,
130 injector,
131 trail: BuildTrail {
132 keys: vec![key.to_string()],
133 },
134 kind: BuildKind::Root,
135 }
136 }
137
138 pub fn nested(&self, key: &str, kind: BuildKind) -> Result<Self> {
141 Ok(Self {
142 resolver: self.resolver.clone(),
143 injector: self.injector.clone(),
144 trail: self.trail.enter(key)?,
145 kind,
146 })
147 }
148
149 pub fn inject(
153 &self,
154 key: &str,
155 table: &mut Table<T, EmptyEntity>,
156 build: SpecTableBuilder<S, T>,
157 ) -> Result<()> {
158 let Some(injector) = &self.injector else {
159 return Ok(());
160 };
161 let db = table.data_source().clone();
162 let nested = |other: &str| -> Result<Table<T, EmptyEntity>> {
163 let env = self.nested(other, BuildKind::Root)?;
164 let resolver = self.resolver.as_ref().ok_or_else(|| {
165 error!(
166 "the injector's table builder needs a spec resolver",
167 key = other
168 )
169 })?;
170 let spec = resolver
171 .resolve(other)
172 .ok_or_else(|| error!("the injector's table builder found no spec", key = other))?;
173 build(&spec, db.clone(), &env)
174 };
175 let cx = InjectCx {
176 key,
177 kind: self.kind.clone(),
178 build: &nested,
179 };
180 injector(&cx, table)
181 }
182}