vantage_vista/source.rs
1use std::pin::Pin;
2
3use async_trait::async_trait;
4use ciborium::Value as CborValue;
5use futures_core::Stream;
6use indexmap::IndexMap;
7use vantage_core::{Result, VantageError, error};
8use vantage_types::Record;
9
10use crate::{capabilities::VistaCapabilities, vista::Vista};
11
12/// Per-driver executor for a `Vista`.
13///
14/// Implementations live in driver crates (vantage-sqlite, vantage-mongodb,
15/// vantage-aws, etc.). Each method receives `&Vista` so the driver can read
16/// the current condition state, columns, and other metadata.
17///
18/// `Id = String` and `Value = ciborium::Value` at this boundary, mirroring
19/// `AnyTable`. CBOR-typed driver ids (Mongo `ObjectId`, Surreal `Thing`)
20/// stringify here. Methods are named with the `_vista_` infix to mirror
21/// `TableSource`'s `_table_` convention; `Vista`'s `ValueSet` impls
22/// delegate by stripping the infix.
23///
24/// `id: &String` (rather than `&str`) is intentional: the upstream
25/// `vantage_dataset::ValueSet` trait family fixes `Id = String` and uses
26/// `&Self::Id` in its signatures, so impls receive `&String` and forward
27/// it through unchanged.
28#[async_trait]
29#[allow(clippy::ptr_arg)]
30pub trait TableShell: Send + Sync + 'static {
31 // ---- ReadableValueSet delegates ----------------------------------------
32
33 async fn list_vista_values(&self, vista: &Vista)
34 -> Result<IndexMap<String, Record<CborValue>>>;
35
36 async fn get_vista_value(
37 &self,
38 vista: &Vista,
39 id: &String,
40 ) -> Result<Option<Record<CborValue>>>;
41
42 async fn get_vista_some_value(
43 &self,
44 vista: &Vista,
45 ) -> Result<Option<(String, Record<CborValue>)>>;
46
47 /// Default implementation wraps `list_vista_values`. Drivers with native
48 /// streaming (cursor-based queries, paginated REST APIs) override.
49 #[allow(clippy::type_complexity)]
50 fn stream_vista_values<'a>(
51 &'a self,
52 vista: &'a Vista,
53 ) -> Pin<Box<dyn Stream<Item = Result<(String, Record<CborValue>)>> + Send + 'a>>
54 where
55 Self: Sync,
56 {
57 Box::pin(async_stream::stream! {
58 match self.list_vista_values(vista).await {
59 Ok(map) => {
60 for item in map {
61 yield Ok(item);
62 }
63 }
64 Err(e) => yield Err(e),
65 }
66 })
67 }
68
69 // ---- WritableValueSet delegates ----------------------------------------
70 //
71 // Default impls return a typed VantageError via `default_error` — drivers
72 // override only what they actually support. The matching `VistaCapabilities`
73 // flag must be set to `true` for any method the driver implements; if the
74 // flag is `true` but the trait method falls through to the default,
75 // `default_error` produces an `Unimplemented`-kind error (placeholder
76 // detected). If the flag is `false`, it produces `Unsupported`. Both are
77 // emitted as tracing events at construction.
78
79 async fn insert_vista_value(
80 &self,
81 vista: &Vista,
82 _id: &String,
83 _record: &Record<CborValue>,
84 ) -> Result<Record<CborValue>> {
85 Err(self.default_error("insert_vista_value", "can_insert", vista))
86 }
87
88 async fn replace_vista_value(
89 &self,
90 vista: &Vista,
91 _id: &String,
92 _record: &Record<CborValue>,
93 ) -> Result<Record<CborValue>> {
94 Err(self.default_error("replace_vista_value", "can_update", vista))
95 }
96
97 async fn patch_vista_value(
98 &self,
99 vista: &Vista,
100 _id: &String,
101 _partial: &Record<CborValue>,
102 ) -> Result<Record<CborValue>> {
103 Err(self.default_error("patch_vista_value", "can_update", vista))
104 }
105
106 async fn delete_vista_value(&self, vista: &Vista, _id: &String) -> Result<()> {
107 Err(self.default_error("delete_vista_value", "can_delete", vista))
108 }
109
110 async fn delete_vista_all_values(&self, vista: &Vista) -> Result<()> {
111 Err(self.default_error("delete_vista_all_values", "can_delete", vista))
112 }
113
114 // ---- InsertableValueSet delegate ---------------------------------------
115
116 async fn insert_vista_return_id_value(
117 &self,
118 vista: &Vista,
119 _record: &Record<CborValue>,
120 ) -> Result<String> {
121 Err(self.default_error("insert_vista_return_id_value", "can_insert", vista))
122 }
123
124 // ---- Aggregates --------------------------------------------------------
125
126 /// Default impl falls back to `list_vista_values` — drivers with native
127 /// count (`SELECT COUNT(*)`, etc.) override.
128 async fn get_vista_count(&self, vista: &Vista) -> Result<i64> {
129 Ok(self.list_vista_values(vista).await?.len() as i64)
130 }
131
132 // ---- Conditions --------------------------------------------------------
133
134 /// Translate `field == value` into the driver's native condition type and
135 /// apply it to the wrapped table. The default impl returns `Unimplemented`
136 /// — every driver is expected to override.
137 ///
138 /// `value` is the universal CBOR carrier; the driver picks the appropriate
139 /// translation (e.g. `cbor_to_bson` for Mongo, `cbor → AnyCsvType` for CSV).
140 fn add_eq_condition(&mut self, _field: &str, _value: &CborValue) -> Result<()> {
141 Err(error!(
142 format!(
143 "add_eq_condition not implemented for '{}'",
144 std::any::type_name::<Self>()
145 ),
146 method = "add_eq_condition",
147 source_type = std::any::type_name::<Self>()
148 )
149 .is_unimplemented())
150 }
151
152 // ---- References --------------------------------------------------------
153
154 /// Resolve a named relation and return the related table as a new
155 /// `Vista`. The default impl returns `Unimplemented` so existing
156 /// drivers compile until they opt in; drivers that wrap a typed
157 /// `Table<T, E>` can delegate to `Table::get_ref`.
158 fn get_ref(&self, relation: &str) -> Result<Vista> {
159 Err(error!(
160 format!(
161 "get_ref not implemented for '{}'",
162 std::any::type_name::<Self>()
163 ),
164 method = "get_ref",
165 relation = relation,
166 source_type = std::any::type_name::<Self>()
167 )
168 .is_unimplemented())
169 }
170
171 // ---- Identity ----------------------------------------------------------
172
173 /// Short human label for the underlying driver (e.g. `"csv"`, `"sqlite"`,
174 /// `"postgres"`, `"mongodb"`). Used for diagnostics and CLI output.
175 /// Drivers should override; the default is a placeholder.
176 fn driver_name(&self) -> &'static str {
177 "unknown"
178 }
179
180 // ---- Capability advertisement -----------------------------------------
181
182 fn capabilities(&self) -> &VistaCapabilities;
183
184 /// Look up a capability flag by name. Used by `default_error` to decide
185 /// between `Unsupported` and `Unimplemented`. Drivers don't normally
186 /// need to override this.
187 fn capability_flag(&self, name: &str) -> bool {
188 let caps = self.capabilities();
189 match name {
190 "can_count" => caps.can_count,
191 "can_insert" => caps.can_insert,
192 "can_update" => caps.can_update,
193 "can_delete" => caps.can_delete,
194 "can_subscribe" => caps.can_subscribe,
195 "can_invalidate" => caps.can_invalidate,
196 _ => false,
197 }
198 }
199
200 /// Build the standard error returned by default trait method impls.
201 ///
202 /// Picks the kind based on the capability flag: a `true` flag means the
203 /// driver advertised support but didn't override the method (placeholder
204 /// → `Unimplemented`); a `false` flag means the driver honestly doesn't
205 /// claim the op (caller should have checked → `Unsupported`).
206 ///
207 /// Both kinds emit a `tracing::error!` at construction with `method`,
208 /// `capability`, `source_type`, and `vista_name` as structured fields.
209 fn default_error(&self, method: &str, capability: &str, vista: &Vista) -> VantageError {
210 let source_type = std::any::type_name::<Self>();
211 let vista_name = vista.name().to_string();
212 if self.capability_flag(capability) {
213 error!(
214 format!(
215 "'{}' is advertised as VistaCapability for '{}' but implementation for '{}' is missing",
216 capability, source_type, method
217 ),
218 method = method,
219 capability = capability,
220 source_type = source_type,
221 vista_name = vista_name
222 )
223 .is_unimplemented()
224 } else {
225 error!(
226 format!(
227 "'{}' is not supported by '{}'; '{}' refused",
228 capability, source_type, method
229 ),
230 method = method,
231 capability = capability,
232 source_type = source_type,
233 vista_name = vista_name
234 )
235 .is_unsupported()
236 }
237 }
238}