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 /// Push a driver-native condition into the wrapped table. The
153 /// caller boxes the condition as `dyn Any` and the driver
154 /// downcasts to its own `T::Condition`. Used by YAML-driven
155 /// relation traversal, where the factory constructs a
156 /// `DeferredFn`-bearing condition outside the value-set surface
157 /// (which only accepts scalar eq) and pushes it through this
158 /// channel. Default is `Unimplemented`.
159 fn add_raw_condition(
160 &mut self,
161 _condition: Box<dyn std::any::Any + Send + Sync>,
162 ) -> Result<()> {
163 Err(error!(
164 format!(
165 "add_raw_condition not implemented for '{}'",
166 std::any::type_name::<Self>()
167 ),
168 method = "add_raw_condition",
169 source_type = std::any::type_name::<Self>()
170 )
171 .is_unimplemented())
172 }
173
174 // ---- References --------------------------------------------------------
175
176 /// Resolve a named relation and return the related table as a new
177 /// `Vista`. The default impl returns `Unimplemented` so existing
178 /// drivers compile until they opt in; drivers that wrap a typed
179 /// `Table<T, E>` can delegate to `Table::get_ref`.
180 fn get_ref(&self, relation: &str) -> Result<Vista> {
181 Err(error!(
182 format!(
183 "get_ref not implemented for '{}'",
184 std::any::type_name::<Self>()
185 ),
186 method = "get_ref",
187 relation = relation,
188 source_type = std::any::type_name::<Self>()
189 )
190 .is_unimplemented())
191 }
192
193 // ---- Identity ----------------------------------------------------------
194
195 /// Short human label for the underlying driver (e.g. `"csv"`, `"sqlite"`,
196 /// `"postgres"`, `"mongodb"`). Used for diagnostics and CLI output.
197 /// Drivers should override; the default is a placeholder.
198 fn driver_name(&self) -> &'static str {
199 "unknown"
200 }
201
202 // ---- Capability advertisement -----------------------------------------
203
204 fn capabilities(&self) -> &VistaCapabilities;
205
206 /// Look up a capability flag by name. Used by `default_error` to decide
207 /// between `Unsupported` and `Unimplemented`. Drivers don't normally
208 /// need to override this.
209 fn capability_flag(&self, name: &str) -> bool {
210 let caps = self.capabilities();
211 match name {
212 "can_count" => caps.can_count,
213 "can_insert" => caps.can_insert,
214 "can_update" => caps.can_update,
215 "can_delete" => caps.can_delete,
216 "can_subscribe" => caps.can_subscribe,
217 "can_invalidate" => caps.can_invalidate,
218 _ => false,
219 }
220 }
221
222 /// Build the standard error returned by default trait method impls.
223 ///
224 /// Picks the kind based on the capability flag: a `true` flag means the
225 /// driver advertised support but didn't override the method (placeholder
226 /// → `Unimplemented`); a `false` flag means the driver honestly doesn't
227 /// claim the op (caller should have checked → `Unsupported`).
228 ///
229 /// Both kinds emit a `tracing::error!` at construction with `method`,
230 /// `capability`, `source_type`, and `vista_name` as structured fields.
231 fn default_error(&self, method: &str, capability: &str, vista: &Vista) -> VantageError {
232 let source_type = std::any::type_name::<Self>();
233 let vista_name = vista.name().to_string();
234 if self.capability_flag(capability) {
235 error!(
236 format!(
237 "'{}' is advertised as VistaCapability for '{}' but implementation for '{}' is missing",
238 capability, source_type, method
239 ),
240 method = method,
241 capability = capability,
242 source_type = source_type,
243 vista_name = vista_name
244 )
245 .is_unimplemented()
246 } else {
247 error!(
248 format!(
249 "'{}' is not supported by '{}'; '{}' refused",
250 capability, source_type, method
251 ),
252 method = method,
253 capability = capability,
254 source_type = source_type,
255 vista_name = vista_name
256 )
257 .is_unsupported()
258 }
259 }
260}