vantage_cmd/vista/
source.rs1use async_trait::async_trait;
12use ciborium::Value as CborValue;
13use indexmap::IndexMap;
14use vantage_core::Result;
15use vantage_dataset::traits::ReadableValueSet;
16use vantage_table::table::Table;
17use vantage_types::{EmptyEntity, Record};
18use vantage_vista::{
19 Capability, Condition, FilterOp, Page, Preview, TableShell, Vista, VistaCapabilities,
20 VistaMetadata, condition_column_error, unknown_relation, unsupported,
21};
22
23use crate::cmd::Cmd;
24use crate::condition::CmdCondition;
25use crate::vista::factory::CmdVistaFactory;
26
27#[derive(Clone)]
28pub struct CmdTableShell {
29 table: Table<Cmd, EmptyEntity>,
30 capabilities: VistaCapabilities,
31 metadata: VistaMetadata,
32}
33
34impl CmdTableShell {
35 pub(crate) fn new(
36 table: Table<Cmd, EmptyEntity>,
37 capabilities: VistaCapabilities,
38 metadata: VistaMetadata,
39 ) -> Self {
40 Self {
41 table,
42 capabilities,
43 metadata,
44 }
45 }
46
47 fn preview_scope(&self) -> serde_json::Value {
50 let script_name = self.table.table_name();
51 let source = self.table.data_source();
52 let conditions: Vec<serde_json::Value> = self
53 .table
54 .conditions()
55 .map(|c| {
56 serde_json::json!({
57 "field": c.field(),
58 "op": c.op(),
59 "value": c.json_value(),
60 })
61 })
62 .collect();
63 serde_json::json!({
64 "command": source.command(),
65 "script_name": script_name,
66 "script": source
67 .spec_for(script_name)
68 .map(|spec| spec.script.to_string())
69 .unwrap_or_else(|e| format!("<no script registered: {e}>")),
70 "scope": {
71 "columns": self.table.columns().keys().collect::<Vec<_>>(),
72 "conditions": conditions,
73 },
74 })
75 }
76}
77
78#[async_trait]
79impl TableShell for CmdTableShell {
80 fn metadata(&self) -> &VistaMetadata {
81 &self.metadata
82 }
83
84 fn capabilities(&self) -> &VistaCapabilities {
85 &self.capabilities
86 }
87
88 fn driver_name(&self) -> &'static str {
89 "cmd"
90 }
91
92 fn preview(&self) -> Preview {
97 Preview::new().with_query(self.preview_scope().to_string())
98 }
99
100 async fn list(&self) -> Result<IndexMap<String, Record<CborValue>>> {
101 self.table.list_values().await
102 }
103
104 async fn get(&self, id: &str) -> Result<Option<Record<CborValue>>> {
109 let has_detail = self
110 .table
111 .data_source()
112 .has_detail_script(self.table.table_name());
113 if self.table.conditions().next().is_none() || !has_detail {
114 return self.table.get_value(id).await;
115 }
116 if !self.table.list_values().await?.contains_key(id) {
117 return Ok(None);
118 }
119 self.table.get_value(id).await
120 }
121
122 async fn count(&self) -> Result<i64> {
123 Ok(self.table.list_values().await?.len() as i64)
124 }
125
126 async fn fetch_window(&self, offset: usize, limit: usize) -> Result<Page> {
129 Ok(Page::from_listing(
130 self.table.list_values().await?,
131 offset,
132 limit,
133 ))
134 }
135
136 fn add_condition(&mut self, condition: &Condition) -> Result<()> {
140 let field = condition.column.as_str();
141 if !self.metadata.columns.contains_key(field) {
142 return Err(condition_column_error(&self.metadata, field));
143 }
144 if condition.op != FilterOp::Eq {
145 return unsupported(Capability::FilterOps);
146 }
147 self.table
148 .add_condition(CmdCondition::eq(field, condition.value.clone()));
149 Ok(())
150 }
151
152 fn get_ref(&self, relation: &str, row: Option<&Record<CborValue>>) -> Result<Vista> {
155 let reference = self
156 .metadata
157 .references
158 .get(relation)
159 .ok_or_else(|| unknown_relation(relation))?;
160 self.table.traverse_erased(reference, row, |target| {
161 CmdVistaFactory::new(self.table.data_source().clone()).from_table(target)
162 })
163 }
164}