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::{
11 capabilities::VistaCapabilities, column::Column, reference::Reference, sort::SortDirection,
12 vista::Vista,
13};
14
15/// Per-driver executor for a `Vista`.
16///
17/// Implementations live in driver crates (vantage-sqlite, vantage-mongodb,
18/// vantage-aws, etc.). Each method receives `&Vista` so the driver can read
19/// the current condition state, columns, and other metadata.
20///
21/// `Id = String` and `Value = ciborium::Value` at this boundary, so every
22/// driver's native id (Mongo `ObjectId`, Surreal `Thing`, …) stringifies
23/// here. Methods are named with the `_vista_` infix to mirror
24/// `TableSource`'s `_table_` convention; `Vista`'s `ValueSet` impls
25/// delegate by stripping the infix.
26///
27/// `id: &String` (rather than `&str`) is intentional: the upstream
28/// `vantage_dataset::ValueSet` trait family fixes `Id = String` and uses
29/// `&Self::Id` in its signatures, so impls receive `&String` and forward
30/// it through unchanged.
31#[async_trait]
32#[allow(clippy::ptr_arg)]
33pub trait TableShell: Send + Sync + 'static {
34 // ---- Schema --------------------------------------------------------------
35 //
36 // The shell owns the schema. `Vista` is a thin wrapper that forwards its
37 // metadata accessors here. No defaults — every impl must answer (an empty
38 // schema is a deliberate choice the impl declares explicitly).
39
40 fn columns(&self) -> &IndexMap<String, Column>;
41
42 fn references(&self) -> &IndexMap<String, Reference>;
43
44 fn id_column(&self) -> Option<&str>;
45
46 // ---- ReadableValueSet delegates ----------------------------------------
47
48 async fn list_vista_values(&self, vista: &Vista)
49 -> Result<IndexMap<String, Record<CborValue>>>;
50
51 async fn get_vista_value(
52 &self,
53 vista: &Vista,
54 id: &String,
55 ) -> Result<Option<Record<CborValue>>>;
56
57 async fn get_vista_some_value(
58 &self,
59 vista: &Vista,
60 ) -> Result<Option<(String, Record<CborValue>)>>;
61
62 /// Default implementation wraps `list_vista_values`. Drivers with native
63 /// streaming (cursor-based queries, paginated REST APIs) override.
64 #[allow(clippy::type_complexity)]
65 fn stream_vista_values<'a>(
66 &'a self,
67 vista: &'a Vista,
68 ) -> Pin<Box<dyn Stream<Item = Result<(String, Record<CborValue>)>> + Send + 'a>>
69 where
70 Self: Sync,
71 {
72 Box::pin(async_stream::stream! {
73 match self.list_vista_values(vista).await {
74 Ok(map) => {
75 for item in map {
76 yield Ok(item);
77 }
78 }
79 Err(e) => yield Err(e),
80 }
81 })
82 }
83
84 // ---- WritableValueSet delegates ----------------------------------------
85 //
86 // Default impls return a typed VantageError via `default_error` — drivers
87 // override only what they actually support. The matching `VistaCapabilities`
88 // flag must be set to `true` for any method the driver implements; if the
89 // flag is `true` but the trait method falls through to the default,
90 // `default_error` produces an `Unimplemented`-kind error (placeholder
91 // detected). If the flag is `false`, it produces `Unsupported`. Both are
92 // emitted as tracing events at construction.
93
94 async fn insert_vista_value(
95 &self,
96 _vista: &Vista,
97 _id: &String,
98 _record: &Record<CborValue>,
99 ) -> Result<Record<CborValue>> {
100 Err(self.default_error("insert_vista_value", "can_insert"))
101 }
102
103 async fn replace_vista_value(
104 &self,
105 _vista: &Vista,
106 _id: &String,
107 _record: &Record<CborValue>,
108 ) -> Result<Record<CborValue>> {
109 Err(self.default_error("replace_vista_value", "can_update"))
110 }
111
112 async fn patch_vista_value(
113 &self,
114 _vista: &Vista,
115 _id: &String,
116 _partial: &Record<CborValue>,
117 ) -> Result<Record<CborValue>> {
118 Err(self.default_error("patch_vista_value", "can_update"))
119 }
120
121 async fn delete_vista_value(&self, _vista: &Vista, _id: &String) -> Result<()> {
122 Err(self.default_error("delete_vista_value", "can_delete"))
123 }
124
125 async fn delete_vista_all_values(&self, _vista: &Vista) -> Result<()> {
126 Err(self.default_error("delete_vista_all_values", "can_delete"))
127 }
128
129 // ---- InsertableValueSet delegate ---------------------------------------
130
131 async fn insert_vista_return_id_value(
132 &self,
133 _vista: &Vista,
134 _record: &Record<CborValue>,
135 ) -> Result<String> {
136 Err(self.default_error("insert_vista_return_id_value", "can_insert"))
137 }
138
139 // ---- Aggregates --------------------------------------------------------
140
141 /// Default impl falls back to `list_vista_values` — drivers with native
142 /// count (`SELECT COUNT(*)`, etc.) override.
143 async fn get_vista_count(&self, vista: &Vista) -> Result<i64> {
144 Ok(self.list_vista_values(vista).await?.len() as i64)
145 }
146
147 // ---- Conditions --------------------------------------------------------
148
149 /// Translate `field == value` into the driver's native condition type and
150 /// apply it to the wrapped table. The default impl returns `Unimplemented`
151 /// — every driver is expected to override.
152 ///
153 /// `value` is the universal CBOR carrier; the driver picks the appropriate
154 /// translation (e.g. `cbor_to_bson` for Mongo, `cbor → AnyCsvType` for CSV).
155 fn add_eq_condition(&mut self, _field: &str, _value: &CborValue) -> Result<()> {
156 Err(error!(
157 format!(
158 "add_eq_condition not implemented for '{}'",
159 std::any::type_name::<Self>()
160 ),
161 method = "add_eq_condition",
162 source_type = std::any::type_name::<Self>()
163 )
164 .is_unimplemented())
165 }
166
167 /// Push a driver-native condition into the wrapped table. The
168 /// caller boxes the condition as `dyn Any` and the driver
169 /// downcasts to its own `T::Condition`. Used by YAML-driven
170 /// relation traversal, where the factory constructs a
171 /// `DeferredFn`-bearing condition outside the value-set surface
172 /// (which only accepts scalar eq) and pushes it through this
173 /// channel. Default is `Unimplemented`.
174 fn add_raw_condition(
175 &mut self,
176 _condition: Box<dyn std::any::Any + Send + Sync>,
177 ) -> Result<()> {
178 Err(error!(
179 format!(
180 "add_raw_condition not implemented for '{}'",
181 std::any::type_name::<Self>()
182 ),
183 method = "add_raw_condition",
184 source_type = std::any::type_name::<Self>()
185 )
186 .is_unimplemented())
187 }
188
189 // ---- Pagination --------------------------------------------------------
190
191 /// Declare how many records constitute one page. Used by both
192 /// [`fetch_page`](Self::fetch_page) and [`fetch_next`](Self::fetch_next).
193 /// Default returns `default_error("set_page_size", "can_set_page_size")`.
194 fn set_page_size(&mut self, _size: usize) -> Result<()> {
195 Err(self.default_error("set_page_size", "can_set_page_size"))
196 }
197
198 /// Fetch a specific page (1-based) using offset-style pagination. The
199 /// per-page count comes from the most recent
200 /// [`set_page_size`](Self::set_page_size).
201 ///
202 /// Drivers without random-access pagination (DynamoDB, most token-paginated
203 /// REST APIs) leave the default in place, which produces `Unsupported`.
204 /// Callers should branch on `vista.capabilities().can_fetch_page` first.
205 async fn fetch_page(
206 &self,
207 _vista: &Vista,
208 _page: usize,
209 ) -> Result<Vec<(String, Record<CborValue>)>> {
210 Err(self.default_error("fetch_page", "can_fetch_page"))
211 }
212
213 /// Cursor-style chain fetch. Pass `None` on the first call; pass the
214 /// previous call's returned token on subsequent calls. Returned token is
215 /// `None` when the result set is exhausted.
216 ///
217 /// The token is **driver-private** — its shape is whatever the backend
218 /// finds convenient (DynamoDB `LastEvaluatedKey` as a CBOR map, REST
219 /// `nextToken` as `CborValue::Text`, offset-based as `CborValue::Integer`).
220 /// Consumers treat it as opaque and round-trip it back unchanged.
221 ///
222 /// Default returns `default_error("fetch_next", "can_fetch_next")`.
223 async fn fetch_next(
224 &self,
225 _vista: &Vista,
226 _token: Option<CborValue>,
227 ) -> Result<(Vec<(String, Record<CborValue>)>, Option<CborValue>)> {
228 Err(self.default_error("fetch_next", "can_fetch_next"))
229 }
230
231 // ---- Quicksearch -------------------------------------------------------
232
233 /// Apply a quicksearch filter — a single string the driver fans out across
234 /// the columns it considers searchable (typically those flagged
235 /// [`SEARCHABLE`](crate::flags::SEARCHABLE), but each driver decides).
236 ///
237 /// **Replace semantics**: calling `add_search` again wipes the previous
238 /// search filter before applying the new one. Default produces
239 /// `Unimplemented` (when `can_search: true`) or `Unsupported` (when
240 /// `can_search: false`).
241 fn add_search(&mut self, _text: &str) -> Result<()> {
242 Err(self.default_error("add_search", "can_search"))
243 }
244
245 /// Drop the search filter previously applied via
246 /// [`add_search`](Self::add_search). Default mirrors `add_search`.
247 fn clear_search(&mut self) -> Result<()> {
248 Err(self.default_error("clear_search", "can_search"))
249 }
250
251 // ---- Ordering ----------------------------------------------------------
252
253 /// Push a single ORDER BY clause onto the wrapped table.
254 ///
255 /// Vista's `add_order` is replace-semantics: the driver shell should clear
256 /// any previously-set order before pushing the new one. Default produces
257 /// `Unimplemented` (when `can_order: true`) or `Unsupported` (when
258 /// `can_order: false`).
259 fn add_order(&mut self, _field: &str, _dir: SortDirection) -> Result<()> {
260 Err(self.default_error("add_order", "can_order"))
261 }
262
263 /// Wipe every order clause. Default mirrors [`add_order`](Self::add_order).
264 fn clear_orders(&mut self) -> Result<()> {
265 Err(self.default_error("clear_orders", "can_order"))
266 }
267
268 // ---- References --------------------------------------------------------
269
270 /// Resolve a same-persistence relation using a known source row, returning
271 /// the related table as a new `Vista`.
272 ///
273 /// Drivers override by forwarding into the wrapped typed `Table`'s
274 /// `get_ref_from_row::<EmptyEntity>(relation, &native_row)` and then
275 /// wrapping the result back as a `Vista` through the driver's factory.
276 /// The default returns `Unimplemented`; cross-persistence refs are
277 /// handled at the `Vista` layer via `Vista::with_foreign` before this
278 /// trait method is reached.
279 fn get_ref(&self, relation: &str, _row: &Record<CborValue>) -> Result<Vista> {
280 Err(error!(
281 format!(
282 "get_ref not implemented for '{}'",
283 std::any::type_name::<Self>()
284 ),
285 method = "get_ref",
286 relation = relation,
287 source_type = std::any::type_name::<Self>()
288 )
289 .is_unimplemented())
290 }
291
292 /// Names + cardinalities of the shell's same-persistence references.
293 /// Derived from [`references`](Self::references) by default; impls
294 /// should rarely need to override.
295 fn get_ref_kinds(&self) -> Vec<(String, crate::reference::ReferenceKind)> {
296 self.references()
297 .iter()
298 .map(|(name, r)| (name.clone(), r.kind))
299 .collect()
300 }
301
302 // ---- Identity ----------------------------------------------------------
303
304 /// Short human label for the underlying driver (e.g. `"csv"`, `"sqlite"`,
305 /// `"postgres"`, `"mongodb"`). Used for diagnostics and CLI output.
306 /// Drivers should override; the default is a placeholder.
307 fn driver_name(&self) -> &'static str {
308 "unknown"
309 }
310
311 // ---- Capability advertisement -----------------------------------------
312
313 fn capabilities(&self) -> &VistaCapabilities;
314
315 /// Look up a capability flag by name. Used by `default_error` to decide
316 /// between `Unsupported` and `Unimplemented`. Drivers don't normally
317 /// need to override this.
318 fn capability_flag(&self, name: &str) -> bool {
319 let caps = self.capabilities();
320 match name {
321 "can_count" => caps.can_count,
322 "can_insert" => caps.can_insert,
323 "can_update" => caps.can_update,
324 "can_delete" => caps.can_delete,
325 "can_subscribe" => caps.can_subscribe,
326 "can_invalidate" => caps.can_invalidate,
327 "can_order" => caps.can_order,
328 "can_search" => caps.can_search,
329 "can_set_page_size" => caps.can_set_page_size,
330 "can_fetch_page" => caps.can_fetch_page,
331 "can_fetch_next" => caps.can_fetch_next,
332 _ => false,
333 }
334 }
335
336 /// Build the standard error returned by default trait method impls.
337 ///
338 /// Picks the kind based on the capability flag: a `true` flag means the
339 /// driver advertised support but didn't override the method (placeholder
340 /// → `Unimplemented`); a `false` flag means the driver honestly doesn't
341 /// claim the op (caller should have checked → `Unsupported`).
342 ///
343 /// Both kinds emit a `tracing::error!` at construction with `method`,
344 /// `capability`, `source_type`, and `vista_name` as structured fields.
345 fn default_error(&self, method: &str, capability: &str) -> VantageError {
346 let source_type = std::any::type_name::<Self>();
347 if self.capability_flag(capability) {
348 error!(
349 format!(
350 "'{}' is advertised as VistaCapability for '{}' but implementation for '{}' is missing",
351 capability, source_type, method
352 ),
353 method = method,
354 capability = capability,
355 source_type = source_type
356 )
357 .is_unimplemented()
358 } else {
359 error!(
360 format!(
361 "'{}' is not supported by '{}'; '{}' refused",
362 capability, source_type, method
363 ),
364 method = method,
365 capability = capability,
366 source_type = source_type
367 )
368 .is_unsupported()
369 }
370 }
371}