Skip to main content

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}