use std::sync::Arc;
use async_trait::async_trait;
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::{Result, error};
use vantage_expressions::Expression;
use vantage_expressions::traits::expressive::{DeferredFn, ExpressiveEnum};
use vantage_table::traits::table_like::TableLike;
use vantage_types::Record;
use vantage_vista::{TableShell, Vista, VistaCapabilities};
use super::any_shell::AnyTableShell;
use super::factory::ModelResolver;
#[derive(Clone)]
pub(crate) struct YamlReference {
pub target: String,
pub kind: YamlReferenceKind,
pub foreign_key: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum YamlReferenceKind {
HasMany,
HasOne,
}
pub struct RestApiTableShell {
pub(crate) table: Box<dyn TableLike<Value = CborValue, Id = String>>,
pub(crate) capabilities: VistaCapabilities,
pub(crate) yaml_refs: IndexMap<String, YamlReference>,
pub(crate) resolver: Option<ModelResolver>,
}
impl RestApiTableShell {
pub(crate) fn new(
table: Box<dyn TableLike<Value = CborValue, Id = String>>,
capabilities: VistaCapabilities,
) -> Self {
Self {
table,
capabilities,
yaml_refs: IndexMap::new(),
resolver: None,
}
}
pub(crate) fn with_yaml_refs(mut self, refs: IndexMap<String, YamlReference>) -> Self {
self.yaml_refs = refs;
self
}
pub(crate) fn with_resolver(mut self, resolver: ModelResolver) -> Self {
self.resolver = Some(resolver);
self
}
fn build_deferred_fk_condition(
parent_table: Box<dyn TableLike<Value = CborValue, Id = String>>,
source_column: String,
target_field: String,
) -> Expression<CborValue> {
let parent_arc = Arc::new(parent_table);
let column = source_column;
let target_field_for_error = target_field.clone();
let deferred = DeferredFn::new(move || {
let parent = parent_arc.clone_box();
let column = column.clone();
let target = target_field_for_error.clone();
Box::pin(async move {
let records = parent.list_values().await?;
let value = records
.values()
.next()
.and_then(|r| r.get(&column))
.cloned()
.ok_or_else(|| {
error!(
"YAML reference: parent yielded no row or column missing",
source_column = column,
target_field = target
)
})?;
Ok(ExpressiveEnum::Scalar(value))
})
});
Expression::new(
"{} = {}",
vec![
ExpressiveEnum::Nested(Expression::new(target_field, vec![])),
ExpressiveEnum::Nested(Expression::new(
"{}",
vec![ExpressiveEnum::Deferred(deferred)],
)),
],
)
}
}
#[async_trait]
impl TableShell for RestApiTableShell {
async fn list_vista_values(
&self,
_vista: &Vista,
) -> Result<IndexMap<String, Record<CborValue>>> {
self.table.list_values().await
}
async fn get_vista_value(
&self,
_vista: &Vista,
id: &String,
) -> Result<Option<Record<CborValue>>> {
let mut data = self.table.list_values().await?;
Ok(data.shift_remove(id))
}
async fn get_vista_some_value(
&self,
_vista: &Vista,
) -> Result<Option<(String, Record<CborValue>)>> {
let data = self.table.list_values().await?;
Ok(data.into_iter().next())
}
async fn get_vista_count(&self, _vista: &Vista) -> Result<i64> {
self.table.get_count().await
}
fn add_eq_condition(&mut self, field: &str, value: &CborValue) -> Result<()> {
let condition = crate::eq_condition(field, value.clone());
self.table.add_condition(Box::new(condition))
}
fn add_raw_condition(&mut self, condition: Box<dyn std::any::Any + Send + Sync>) -> Result<()> {
self.table.add_condition(condition)
}
fn get_ref(&self, relation: &str) -> Result<Vista> {
if let Some(yref) = self.yaml_refs.get(relation) {
let resolver = self.resolver.as_ref().ok_or_else(|| {
error!(
"YAML reference requires a model resolver — call \
`with_model_resolver` on the factory or register \
the target spec via `register_yaml`",
relation = relation
)
})?;
let mut child = resolver(&yref.target)?;
let (source_column, target_field) = match yref.kind {
YamlReferenceKind::HasMany => {
let parent_id = self.table.id_field_name().ok_or_else(|| {
error!(
"YAML has_many reference needs the parent to have an id field",
relation = relation
)
})?;
(parent_id, yref.foreign_key.clone())
}
YamlReferenceKind::HasOne => {
let child_id = child
.get_id_column()
.map(str::to_string)
.unwrap_or_else(|| "id".to_string());
(yref.foreign_key.clone(), child_id)
}
};
let parent_clone = self.table.clone_box();
let condition =
Self::build_deferred_fk_condition(parent_clone, source_column, target_field);
child.add_raw_condition(condition)?;
return Ok(child);
}
let any_table = self.table.get_ref(relation)?;
AnyTableShell::into_vista(any_table)
}
fn capabilities(&self) -> &VistaCapabilities {
&self.capabilities
}
fn driver_name(&self) -> &'static str {
"rest-api"
}
}