use async_trait::async_trait;
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::Result;
use vantage_dataset::traits::ReadableValueSet;
use vantage_table::table::Table;
use vantage_types::{EmptyEntity, Record};
use vantage_vista::{
Capability, Condition, FilterOp, Page, Preview, TableShell, Vista, VistaCapabilities,
VistaMetadata, condition_column_error, unknown_relation, unsupported,
};
use crate::cmd::Cmd;
use crate::condition::CmdCondition;
use crate::vista::factory::CmdVistaFactory;
#[derive(Clone)]
pub struct CmdTableShell {
table: Table<Cmd, EmptyEntity>,
capabilities: VistaCapabilities,
metadata: VistaMetadata,
}
impl CmdTableShell {
pub(crate) fn new(
table: Table<Cmd, EmptyEntity>,
capabilities: VistaCapabilities,
metadata: VistaMetadata,
) -> Self {
Self {
table,
capabilities,
metadata,
}
}
fn preview_scope(&self) -> serde_json::Value {
let script_name = self.table.table_name();
let source = self.table.data_source();
let conditions: Vec<serde_json::Value> = self
.table
.conditions()
.map(|c| {
serde_json::json!({
"field": c.field(),
"op": c.op(),
"value": c.json_value(),
})
})
.collect();
serde_json::json!({
"command": source.command(),
"script_name": script_name,
"script": source
.spec_for(script_name)
.map(|spec| spec.script.to_string())
.unwrap_or_else(|e| format!("<no script registered: {e}>")),
"scope": {
"columns": self.table.columns().keys().collect::<Vec<_>>(),
"conditions": conditions,
},
})
}
}
#[async_trait]
impl TableShell for CmdTableShell {
fn metadata(&self) -> &VistaMetadata {
&self.metadata
}
fn capabilities(&self) -> &VistaCapabilities {
&self.capabilities
}
fn driver_name(&self) -> &'static str {
"cmd"
}
fn preview(&self) -> Preview {
Preview::new().with_query(self.preview_scope().to_string())
}
async fn list(&self) -> Result<IndexMap<String, Record<CborValue>>> {
self.table.list_values().await
}
async fn get(&self, id: &str) -> Result<Option<Record<CborValue>>> {
let has_detail = self
.table
.data_source()
.has_detail_script(self.table.table_name());
if self.table.conditions().next().is_none() || !has_detail {
return self.table.get_value(id).await;
}
if !self.table.list_values().await?.contains_key(id) {
return Ok(None);
}
self.table.get_value(id).await
}
async fn count(&self) -> Result<i64> {
Ok(self.table.list_values().await?.len() as i64)
}
async fn fetch_window(&self, offset: usize, limit: usize) -> Result<Page> {
Ok(Page::from_listing(
self.table.list_values().await?,
offset,
limit,
))
}
fn add_condition(&mut self, condition: &Condition) -> Result<()> {
let field = condition.column.as_str();
if !self.metadata.columns.contains_key(field) {
return Err(condition_column_error(&self.metadata, field));
}
if condition.op != FilterOp::Eq {
return unsupported(Capability::FilterOps);
}
self.table
.add_condition(CmdCondition::eq(field, condition.value.clone()));
Ok(())
}
fn get_ref(&self, relation: &str, row: Option<&Record<CborValue>>) -> Result<Vista> {
let reference = self
.metadata
.references
.get(relation)
.ok_or_else(|| unknown_relation(relation))?;
self.table.traverse_erased(reference, row, |target| {
CmdVistaFactory::new(self.table.data_source().clone()).from_table(target)
})
}
}