use std::fmt;
use std::sync::Arc;
use vantage_core::{Result, error};
use vantage_types::EmptyEntity;
use vantage_vista::SpecResolver;
use crate::table::Table;
use crate::traits::table_source::TableSource;
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BuildKind {
Root,
Reference { from: String, relation: String },
Base { derived: String },
}
#[non_exhaustive]
pub struct InjectCx<'a, T: TableSource> {
pub key: &'a str,
pub kind: BuildKind,
pub build: &'a dyn Fn(&str) -> Result<Table<T, EmptyEntity>>,
}
pub type TableInjector<T> =
Arc<dyn Fn(&InjectCx<'_, T>, &mut Table<T, EmptyEntity>) -> Result<()> + Send + Sync>;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BuildTrail {
keys: Vec<String>,
}
impl BuildTrail {
pub fn enter(&self, key: &str) -> Result<BuildTrail> {
if self.keys.iter().any(|k| k == key) {
let path = self
.keys
.iter()
.map(String::as_str)
.chain([key])
.collect::<Vec<_>>()
.join(" -> ");
return Err(error!("table build cycle", path = path));
}
let mut keys = self.keys.clone();
keys.push(key.to_string());
Ok(BuildTrail { keys })
}
pub fn keys(&self) -> &[String] {
&self.keys
}
}
pub type SpecTableBuilder<S, T> = fn(&S, T, &BuildEnv<S, T>) -> Result<Table<T, EmptyEntity>>;
pub struct BuildEnv<S, T: TableSource> {
pub resolver: Option<Arc<dyn SpecResolver<S>>>,
pub injector: Option<TableInjector<T>>,
pub trail: BuildTrail,
pub kind: BuildKind,
}
impl<S, T: TableSource> Clone for BuildEnv<S, T> {
fn clone(&self) -> Self {
Self {
resolver: self.resolver.clone(),
injector: self.injector.clone(),
trail: self.trail.clone(),
kind: self.kind.clone(),
}
}
}
impl<S, T: TableSource> fmt::Debug for BuildEnv<S, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BuildEnv")
.field("resolver", &self.resolver.is_some())
.field("injector", &self.injector.is_some())
.field("trail", &self.trail)
.field("kind", &self.kind)
.finish()
}
}
impl<S: 'static, T: TableSource> BuildEnv<S, T> {
pub fn root(
key: &str,
resolver: Option<Arc<dyn SpecResolver<S>>>,
injector: Option<TableInjector<T>>,
) -> Self {
Self {
resolver,
injector,
trail: BuildTrail {
keys: vec![key.to_string()],
},
kind: BuildKind::Root,
}
}
pub fn nested(&self, key: &str, kind: BuildKind) -> Result<Self> {
Ok(Self {
resolver: self.resolver.clone(),
injector: self.injector.clone(),
trail: self.trail.enter(key)?,
kind,
})
}
pub fn inject(
&self,
key: &str,
table: &mut Table<T, EmptyEntity>,
build: SpecTableBuilder<S, T>,
) -> Result<()> {
let Some(injector) = &self.injector else {
return Ok(());
};
let db = table.data_source().clone();
let nested = |other: &str| -> Result<Table<T, EmptyEntity>> {
let env = self.nested(other, BuildKind::Root)?;
let resolver = self.resolver.as_ref().ok_or_else(|| {
error!(
"the injector's table builder needs a spec resolver",
key = other
)
})?;
let spec = resolver
.resolve(other)
.ok_or_else(|| error!("the injector's table builder found no spec", key = other))?;
build(&spec, db.clone(), &env)
};
let cx = InjectCx {
key,
kind: self.kind.clone(),
build: &nested,
};
injector(&cx, table)
}
}