Skip to main content

vantage_table/
any.rs

1//! Type-erased table wrapper with downcasting support
2//!
3//! `AnyTable` provides a way to store tables of different types uniformly
4//! while preserving the ability to recover the concrete type through downcasting.
5//!
6//! Tables whose value/id types differ from `serde_json::Value`/`String` can still
7//! be wrapped via [`AnyTable::from_table`] as long as the value type implements
8//! `Into<Value> + From<Value>` and the id type implements `Display + From<String>`.
9
10use std::any::TypeId;
11use std::fmt::Display;
12
13use async_trait::async_trait;
14use indexmap::IndexMap;
15use serde_json::Value;
16use vantage_core::{Result, error};
17use vantage_dataset::traits::{ReadableValueSet, ValueSet, WritableValueSet};
18use vantage_types::{Entity, Record};
19
20// Type alias for cleaner code
21pub type AnyRecord = Record<Value>;
22
23use crate::{
24    conditions::ConditionHandle,
25    pagination::Pagination,
26    table::Table,
27    traits::{table_like::TableLike, table_source::TableSource},
28};
29
30/// Type-erased table that can be downcast to concrete `Table<T, E>`
31/// Works with AnyRecord (which uses serde_json::Value)
32pub struct AnyTable {
33    inner: Box<dyn TableLike<Value = Value, Id = String>>,
34    datasource_type_id: TypeId,
35    entity_type_id: TypeId,
36    datasource_name: &'static str,
37    entity_name: &'static str,
38}
39
40impl AnyTable {
41    /// Create a new AnyTable from a concrete table
42    /// Only works with tables that use serde_json::Value as their Value type
43    pub fn new<T: TableSource<Value = Value, Id = String> + 'static, E: Entity<Value> + 'static>(
44        table: Table<T, E>,
45    ) -> Self {
46        Self {
47            inner: Box::new(table),
48            datasource_type_id: TypeId::of::<T>(),
49            entity_type_id: TypeId::of::<E>(),
50            datasource_name: std::any::type_name::<T>(),
51            entity_name: std::any::type_name::<E>(),
52        }
53    }
54
55    /// Create an AnyTable from a table with any value/id types that convert to/from JSON.
56    ///
57    /// This wraps the table in an internal adapter that converts values on the fly,
58    /// so any `TableSource` can be used with the unified `AnyTable` interface.
59    pub fn from_table<T, E>(table: Table<T, E>) -> Self
60    where
61        T: TableSource + 'static,
62        T::Value: Into<Value> + From<Value>,
63        T::Id: Display + From<String>,
64        E: Entity<T::Value> + 'static,
65    {
66        Self {
67            inner: Box::new(JsonAdapter { inner: table }),
68            datasource_type_id: TypeId::of::<T>(),
69            entity_type_id: TypeId::of::<E>(),
70            datasource_name: std::any::type_name::<T>(),
71            entity_name: std::any::type_name::<E>(),
72        }
73    }
74
75    /// Attempt to downcast to a concrete `Table<T, E>`
76    ///
77    /// Returns `Err(self)` if the type doesn't match, allowing recovery
78    pub fn downcast<
79        T: TableSource<Value = Value, Id = String> + 'static,
80        E: Entity<Value> + 'static,
81    >(
82        self,
83    ) -> Result<Table<T, E>> {
84        // Check TypeIds for better error messages
85        if self.datasource_type_id != TypeId::of::<T>() {
86            let expected = std::any::type_name::<T>();
87            return Err(error!(
88                "DataSource type mismatch",
89                expected = expected,
90                actual = self.datasource_name
91            ));
92        }
93        if self.entity_type_id != TypeId::of::<E>() {
94            let expected = std::any::type_name::<E>();
95            return Err(error!(
96                "Entity type mismatch",
97                expected = expected,
98                actual = self.entity_name
99            ));
100        }
101
102        // Perform the actual downcast
103        self.inner
104            .into_any()
105            .downcast::<Table<T, E>>()
106            .map(|boxed| *boxed)
107            .map_err(|_| error!("Failed to downcast table"))
108    }
109
110    /// Get the datasource type name for debugging
111    pub fn datasource_name(&self) -> &str {
112        self.datasource_name
113    }
114
115    /// Get the entity type name for debugging
116    pub fn entity_name(&self) -> &str {
117        self.entity_name
118    }
119
120    /// Get the datasource TypeId
121    pub fn datasource_type_id(&self) -> TypeId {
122        self.datasource_type_id
123    }
124
125    /// Get the entity TypeId
126    pub fn entity_type_id(&self) -> TypeId {
127        self.entity_type_id
128    }
129
130    /// Check if this table matches the given types
131    pub fn is_type<T: TableSource + 'static, E: Entity<Value> + 'static>(&self) -> bool {
132        self.datasource_type_id == TypeId::of::<T>() && self.entity_type_id == TypeId::of::<E>()
133    }
134}
135
136impl Clone for AnyTable {
137    fn clone(&self) -> Self {
138        Self {
139            inner: self.inner.clone_box(),
140            datasource_type_id: self.datasource_type_id,
141            entity_type_id: self.entity_type_id,
142            datasource_name: self.datasource_name,
143            entity_name: self.entity_name,
144        }
145    }
146}
147
148impl std::fmt::Debug for AnyTable {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        f.debug_struct("AnyTable")
151            .field("datasource", &self.datasource_name)
152            .field("entity", &self.entity_name)
153            .finish()
154    }
155}
156
157// Implement ValueSet first
158impl ValueSet for AnyTable {
159    type Id = String;
160    type Value = Value;
161}
162
163// Implement ReadableValueSet by delegating to inner TableLike
164#[async_trait]
165impl ReadableValueSet for AnyTable {
166    async fn list_values(&self) -> Result<IndexMap<Self::Id, Record<Self::Value>>> {
167        self.inner.list_values().await
168    }
169
170    async fn get_value(&self, id: &Self::Id) -> Result<Option<Record<Self::Value>>> {
171        self.inner.get_value(id).await
172    }
173
174    async fn get_some_value(&self) -> Result<Option<(Self::Id, Record<Self::Value>)>> {
175        self.inner.get_some_value().await
176    }
177}
178
179// Implement WritableValueSet by delegating to inner TableLike
180#[async_trait]
181impl WritableValueSet for AnyTable {
182    async fn insert_value(
183        &self,
184        id: &Self::Id,
185        record: &Record<Self::Value>,
186    ) -> Result<Record<Self::Value>> {
187        self.inner.insert_value(id, record).await
188    }
189
190    async fn replace_value(
191        &self,
192        id: &Self::Id,
193        record: &Record<Self::Value>,
194    ) -> Result<Record<Self::Value>> {
195        self.inner.replace_value(id, record).await
196    }
197
198    async fn patch_value(
199        &self,
200        id: &Self::Id,
201        partial: &Record<Self::Value>,
202    ) -> Result<Record<Self::Value>> {
203        self.inner.patch_value(id, partial).await
204    }
205
206    async fn delete(&self, id: &Self::Id) -> Result<()> {
207        self.inner.delete(id).await
208    }
209
210    async fn delete_all(&self) -> Result<()> {
211        self.inner.delete_all().await
212    }
213}
214
215// Implement TableLike by delegating to inner
216#[async_trait]
217impl TableLike for AnyTable {
218    fn table_name(&self) -> &str {
219        self.inner.table_name()
220    }
221
222    fn table_alias(&self) -> &str {
223        self.inner.table_alias()
224    }
225
226    fn column_names(&self) -> Vec<String> {
227        self.inner.column_names()
228    }
229
230    fn add_condition(&mut self, condition: Box<dyn std::any::Any + Send + Sync>) -> Result<()> {
231        self.inner.add_condition(condition)
232    }
233
234    fn temp_add_condition(
235        &mut self,
236        condition: vantage_expressions::AnyExpression,
237    ) -> Result<ConditionHandle> {
238        self.inner.temp_add_condition(condition)
239    }
240
241    fn temp_remove_condition(&mut self, handle: ConditionHandle) -> Result<()> {
242        self.inner.temp_remove_condition(handle)
243    }
244
245    fn search_expression(&self, search_value: &str) -> Result<vantage_expressions::AnyExpression> {
246        self.inner.search_expression(search_value)
247    }
248
249    fn clone_box(&self) -> Box<dyn TableLike<Value = Self::Value, Id = Self::Id>> {
250        Box::new(self.clone())
251    }
252
253    fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> {
254        self
255    }
256
257    fn as_any_ref(&self) -> &dyn std::any::Any {
258        self
259    }
260
261    fn set_pagination(&mut self, pagination: Option<Pagination>) {
262        self.inner.set_pagination(pagination)
263    }
264
265    fn get_pagination(&self) -> Option<&Pagination> {
266        self.inner.get_pagination()
267    }
268
269    async fn get_count(&self) -> vantage_core::Result<i64> {
270        self.inner.get_count().await
271    }
272}
273
274impl AnyTable {
275    /// Configure pagination using a callback
276    pub fn with_pagination<F>(&mut self, func: F)
277    where
278        F: FnOnce(&mut Pagination),
279    {
280        let mut pagination = self.inner.get_pagination().copied().unwrap_or_default();
281        func(&mut pagination);
282        self.inner.set_pagination(Some(pagination));
283    }
284}
285
286// ── JsonAdapter: blanket bridge for non-JSON table types ────────────────
287
288/// Wraps a `Table<T, E>` whose value/id types are not `serde_json::Value`/`String`,
289/// converting on the fly so it can satisfy `TableLike<Value = Value, Id = String>`.
290struct JsonAdapter<T: TableSource, E: Entity<T::Value>> {
291    inner: Table<T, E>,
292}
293
294impl<T: TableSource, E: Entity<T::Value>> Clone for JsonAdapter<T, E> {
295    fn clone(&self) -> Self {
296        Self {
297            inner: self.inner.clone(),
298        }
299    }
300}
301
302impl<T, E> JsonAdapter<T, E>
303where
304    T: TableSource,
305    T::Value: Into<Value>,
306    T::Id: Display,
307    E: Entity<T::Value>,
308{
309    fn convert_record(record: Record<T::Value>) -> Record<Value> {
310        record.into_iter().map(|(k, v)| (k, v.into())).collect()
311    }
312
313    fn convert_record_back(record: &Record<Value>) -> Record<T::Value>
314    where
315        T::Value: From<Value>,
316    {
317        record
318            .iter()
319            .map(|(k, v)| (k.clone(), T::Value::from(v.clone())))
320            .collect()
321    }
322}
323
324impl<T, E> ValueSet for JsonAdapter<T, E>
325where
326    T: TableSource,
327    E: Entity<T::Value>,
328{
329    type Id = String;
330    type Value = Value;
331}
332
333#[async_trait]
334impl<T, E> ReadableValueSet for JsonAdapter<T, E>
335where
336    T: TableSource,
337    T::Value: Into<Value>,
338    T::Id: Display + From<String>,
339    E: Entity<T::Value>,
340{
341    async fn list_values(&self) -> Result<IndexMap<String, Record<Value>>> {
342        let raw = self
343            .inner
344            .data_source()
345            .list_table_values(&self.inner)
346            .await?;
347        Ok(raw
348            .into_iter()
349            .map(|(id, rec)| (id.to_string(), Self::convert_record(rec)))
350            .collect())
351    }
352
353    async fn get_value(&self, id: &String) -> Result<Option<Record<Value>>> {
354        let native_id: T::Id = id.clone().into();
355        let rec = self
356            .inner
357            .data_source()
358            .get_table_value(&self.inner, &native_id)
359            .await?;
360        Ok(rec.map(Self::convert_record))
361    }
362
363    async fn get_some_value(&self) -> Result<Option<(String, Record<Value>)>> {
364        let result = self
365            .inner
366            .data_source()
367            .get_table_some_value(&self.inner)
368            .await?;
369        Ok(result.map(|(id, rec)| (id.to_string(), Self::convert_record(rec))))
370    }
371}
372
373#[async_trait]
374impl<T, E> WritableValueSet for JsonAdapter<T, E>
375where
376    T: TableSource,
377    T::Value: Into<Value> + From<Value>,
378    T::Id: Display + From<String>,
379    E: Entity<T::Value>,
380{
381    async fn insert_value(&self, id: &String, record: &Record<Value>) -> Result<Record<Value>> {
382        let native_id: T::Id = id.clone().into();
383        let native_rec = Self::convert_record_back(record);
384        let returned = self
385            .inner
386            .data_source()
387            .insert_table_value(&self.inner, &native_id, &native_rec)
388            .await?;
389        Ok(Self::convert_record(returned))
390    }
391
392    async fn replace_value(&self, id: &String, record: &Record<Value>) -> Result<Record<Value>> {
393        let native_id: T::Id = id.clone().into();
394        let native_rec = Self::convert_record_back(record);
395        let returned = self
396            .inner
397            .data_source()
398            .replace_table_value(&self.inner, &native_id, &native_rec)
399            .await?;
400        Ok(Self::convert_record(returned))
401    }
402
403    async fn patch_value(&self, id: &String, partial: &Record<Value>) -> Result<Record<Value>> {
404        let native_id: T::Id = id.clone().into();
405        let native_rec = Self::convert_record_back(partial);
406        let returned = self
407            .inner
408            .data_source()
409            .patch_table_value(&self.inner, &native_id, &native_rec)
410            .await?;
411        Ok(Self::convert_record(returned))
412    }
413
414    async fn delete(&self, id: &String) -> Result<()> {
415        let native_id: T::Id = id.clone().into();
416        self.inner
417            .data_source()
418            .delete_table_value(&self.inner, &native_id)
419            .await
420    }
421
422    async fn delete_all(&self) -> Result<()> {
423        self.inner
424            .data_source()
425            .delete_table_all_values(&self.inner)
426            .await
427    }
428}
429
430#[async_trait]
431impl<T, E> TableLike for JsonAdapter<T, E>
432where
433    T: TableSource + 'static,
434    T::Value: Into<Value> + From<Value>,
435    T::Id: Display + From<String>,
436    E: Entity<T::Value> + 'static,
437{
438    fn table_name(&self) -> &str {
439        self.inner.table_name()
440    }
441
442    fn table_alias(&self) -> &str {
443        self.inner.table_name()
444    }
445
446    fn column_names(&self) -> Vec<String> {
447        self.inner.columns().keys().cloned().collect()
448    }
449
450    fn add_condition(&mut self, _condition: Box<dyn std::any::Any + Send + Sync>) -> Result<()> {
451        Err(error!("add_condition not supported through JsonAdapter"))
452    }
453
454    fn temp_add_condition(
455        &mut self,
456        _condition: vantage_expressions::AnyExpression,
457    ) -> Result<ConditionHandle> {
458        Err(error!(
459            "temp_add_condition not supported through JsonAdapter"
460        ))
461    }
462
463    fn temp_remove_condition(&mut self, _handle: ConditionHandle) -> Result<()> {
464        Err(error!(
465            "temp_remove_condition not supported through JsonAdapter"
466        ))
467    }
468
469    fn search_expression(&self, _search_value: &str) -> Result<vantage_expressions::AnyExpression> {
470        Err(error!(
471            "search_expression not supported through JsonAdapter"
472        ))
473    }
474
475    fn clone_box(&self) -> Box<dyn TableLike<Value = Value, Id = String>> {
476        Box::new(self.clone())
477    }
478
479    fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> {
480        self
481    }
482
483    fn as_any_ref(&self) -> &dyn std::any::Any {
484        self
485    }
486
487    fn set_pagination(&mut self, pagination: Option<Pagination>) {
488        self.inner.set_pagination(pagination);
489    }
490
491    fn get_pagination(&self) -> Option<&Pagination> {
492        self.inner.pagination()
493    }
494
495    async fn get_count(&self) -> Result<i64> {
496        self.inner.data_source().get_table_count(&self.inner).await
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use crate::mocks::mock_table_source::MockTableSource;
503
504    use super::*;
505
506    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
507    struct TestEntity {
508        id: i32,
509        name: String,
510    }
511
512    #[test]
513    fn test_anytable_creation_and_downcast() {
514        let ds = MockTableSource::new();
515        let table = Table::<MockTableSource, TestEntity>::new("test", ds);
516        let any = AnyTable::new(table.clone());
517
518        assert_eq!(
519            any.datasource_name(),
520            std::any::type_name::<MockTableSource>()
521        );
522        assert_eq!(any.entity_name(), std::any::type_name::<TestEntity>());
523
524        // Successful downcast
525        let recovered = any.downcast::<MockTableSource, TestEntity>().unwrap();
526        assert_eq!(recovered.table_name(), "test");
527    }
528
529    #[test]
530    fn test_anytable_downcast_wrong_entity() {
531        let ds = MockTableSource::new();
532        let table = Table::<MockTableSource, TestEntity>::new("test", ds);
533        let any = AnyTable::new(table);
534
535        // Try to downcast to wrong entity type - use a different entity
536        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
537        struct DifferentEntity;
538
539        // DifferentEntity automatically gets Entity via blanket impl
540
541        let result = any.downcast::<MockTableSource, DifferentEntity>();
542        assert!(result.is_err());
543    }
544
545    #[test]
546    fn test_anytable_is_type() {
547        let ds = MockTableSource::new();
548        let table = Table::<MockTableSource, TestEntity>::new("test", ds);
549        let any = AnyTable::new(table);
550
551        assert!(any.is_type::<MockTableSource, TestEntity>());
552        // Test with different entity type
553        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
554        struct OtherEntity;
555
556        assert!(!any.is_type::<MockTableSource, OtherEntity>());
557    }
558
559    #[test]
560    fn test_anytable_debug() {
561        let ds = MockTableSource::new();
562        let table = Table::<MockTableSource, TestEntity>::new("test", ds);
563        let any = AnyTable::new(table);
564
565        let debug_str = format!("{:?}", any);
566        assert!(debug_str.contains("AnyTable"));
567        assert!(debug_str.contains("datasource"));
568        assert!(debug_str.contains("entity"));
569    }
570}