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