use indexmap::IndexMap;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use vantage_core::Result;
use crate::column::{Column, ColumnFlag};
use crate::reference::{JoinKey, ReferenceKind};
mod lowering;
pub use lowering::{SpecResolver, resolve_base_spec};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct NoExtras {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(
serialize = "T: Serialize, C: Serialize, R: Serialize",
deserialize = "T: DeserializeOwned + Default, C: DeserializeOwned + Default, R: DeserializeOwned + Default"
))]
#[non_exhaustive]
pub struct VistaSpec<T = NoExtras, C = NoExtras, R = NoExtras> {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub datasource: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id_column: Option<String>,
pub columns: IndexMap<String, ColumnSpec<C>>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub references: IndexMap<String, ReferenceSpec<R>>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub contained: IndexMap<String, ContainedYaml<C>>,
#[serde(flatten, default)]
pub driver: T,
}
impl<T: Default, C, R> VistaSpec<T, C, R> {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
datasource: None,
id_column: None,
columns: IndexMap::new(),
references: IndexMap::new(),
contained: IndexMap::new(),
driver: T::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(
deny_unknown_fields,
bound(
serialize = "C: Serialize",
deserialize = "C: DeserializeOwned + Default"
)
)]
#[non_exhaustive]
pub struct ContainedYaml<C = NoExtras> {
pub host_column: String,
#[serde(default = "contains_one")]
pub kind: ReferenceKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id_column: Option<String>,
pub columns: IndexMap<String, ColumnSpec<C>>,
}
fn contains_one() -> ReferenceKind {
ReferenceKind::ContainsOne
}
fn has_one() -> ReferenceKind {
ReferenceKind::HasOne
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(
serialize = "C: Serialize",
deserialize = "C: DeserializeOwned + Default"
))]
#[non_exhaustive]
pub struct ColumnSpec<C = NoExtras> {
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub col_type: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub flags: Vec<ColumnFlag>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub references: Option<ReferenceSugar>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lazy: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expr: Option<String>,
#[serde(flatten, default)]
pub driver: C,
}
impl<C: Default> ColumnSpec<C> {
pub fn new() -> Self {
Self {
col_type: None,
flags: Vec::new(),
references: None,
lazy: None,
expr: None,
driver: C::default(),
}
}
pub fn with_type(mut self, ty: impl Into<String>) -> Self {
self.col_type = Some(ty.into());
self
}
pub fn with_flag(mut self, flag: impl Into<ColumnFlag>) -> Self {
self.flags.push(ColumnFlag::from(flag.into().as_str()));
self
}
pub fn with_lazy(mut self, script: impl Into<String>) -> Self {
self.lazy = Some(script.into());
self
}
pub fn with_expr(mut self, script: impl Into<String>) -> Self {
self.expr = Some(script.into());
self
}
}
impl<C> ColumnSpec<C> {
pub fn lazy_column(&self, name: &str) -> Result<Option<Column>> {
let Some(code) = &self.lazy else {
return Ok(None);
};
let typed = match &self.col_type {
Some(ty) => Column::new(name, ty),
None => Column::untyped(name),
};
let column = self.flags.iter().cloned().fold(typed, Column::with_flag);
computed(column, code).map(Some)
}
}
#[cfg(feature = "rhai")]
fn computed(column: Column, code: &str) -> Result<Column> {
column.with_expression(code)
}
#[cfg(not(feature = "rhai"))]
fn computed(column: Column, _code: &str) -> Result<Column> {
Err(vantage_core::error!(
"Column declares a `lazy:` script but vantage-vista was built without the `rhai` feature",
column = column.name
))
}
impl<C: Default> Default for ColumnSpec<C> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(
serialize = "R: Serialize",
deserialize = "R: DeserializeOwned + Default"
))]
#[non_exhaustive]
pub struct ReferenceSpec<R = NoExtras> {
pub table: String,
#[serde(default = "has_one")]
pub kind: ReferenceKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub foreign_key: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub keys: Vec<JoinKey>,
#[serde(flatten, default)]
pub driver: R,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum ReferenceSugar {
Sugar(String),
Full(ReferenceSpec<NoExtras>),
}
#[cfg(test)]
mod tests;