Skip to main content

vantage_api_client/vista/
source.rs

1//! `RestApiTableShell` — owns the typed `Table<RestApi, E>` behind a
2//! `dyn TableLike` so the original entity type stays attached for
3//! reference traversal.
4//!
5//! `RestApi` already speaks `ciborium::Value` natively (the HTTP body
6//! is converted at the fetch boundary), so the shell is a pass-through
7//! with no per-record translation.
8//!
9//! Why `Box<dyn TableLike>` and not `Table<RestApi, EmptyEntity>`?
10//! `with_many` / `with_one` register references whose `SourceE` is the
11//! original entity (`User`, `Album`, …). At traversal time
12//! `HasMany::resolve_as_any` downcasts `Table<T, SourceE>` against the
13//! stored value; erasing the entity to `EmptyEntity` would break that
14//! downcast.
15
16use async_trait::async_trait;
17use ciborium::Value as CborValue;
18use indexmap::IndexMap;
19use vantage_core::Result;
20use vantage_table::traits::table_like::TableLike;
21use vantage_types::Record;
22use vantage_vista::{TableShell, Vista, VistaCapabilities};
23
24use super::any_shell::AnyTableShell;
25
26pub struct RestApiTableShell {
27    pub(crate) table: Box<dyn TableLike<Value = CborValue, Id = String>>,
28    pub(crate) capabilities: VistaCapabilities,
29}
30
31impl RestApiTableShell {
32    pub(crate) fn new(
33        table: Box<dyn TableLike<Value = CborValue, Id = String>>,
34        capabilities: VistaCapabilities,
35    ) -> Self {
36        Self {
37            table,
38            capabilities,
39        }
40    }
41}
42
43#[async_trait]
44impl TableShell for RestApiTableShell {
45    async fn list_vista_values(
46        &self,
47        _vista: &Vista,
48    ) -> Result<IndexMap<String, Record<CborValue>>> {
49        self.table.list_values().await
50    }
51
52    async fn get_vista_value(
53        &self,
54        _vista: &Vista,
55        id: &String,
56    ) -> Result<Option<Record<CborValue>>> {
57        let mut data = self.table.list_values().await?;
58        Ok(data.shift_remove(id))
59    }
60
61    async fn get_vista_some_value(
62        &self,
63        _vista: &Vista,
64    ) -> Result<Option<(String, Record<CborValue>)>> {
65        let data = self.table.list_values().await?;
66        Ok(data.into_iter().next())
67    }
68
69    async fn get_vista_count(&self, _vista: &Vista) -> Result<i64> {
70        self.table.get_count().await
71    }
72
73    fn add_eq_condition(&mut self, field: &str, value: &CborValue) -> Result<()> {
74        // Build a typed `Expression<CborValue>` and hand it through
75        // the type-erased `add_condition` API — `Table::add_condition`
76        // downcasts it back to `RestApi::Condition` on the other side.
77        let condition = crate::eq_condition(field, value.clone());
78        self.table.add_condition(Box::new(condition))
79    }
80
81    fn get_ref(&self, relation: &str) -> Result<Vista> {
82        // `TableLike::get_ref` delegates to `Table<T, E>::get_ref`,
83        // which uses the typed-table reference machinery. The result
84        // already carries the right conditions (parent-narrowing eq
85        // translated by `related_in_condition`) and the right table
86        // name (possibly a URI template, since the child factory
87        // chose one). Wrap it in a Vista so generic code can keep
88        // driving it.
89        let any_table = self.table.get_ref(relation)?;
90        AnyTableShell::into_vista(any_table)
91    }
92
93    fn capabilities(&self) -> &VistaCapabilities {
94        &self.capabilities
95    }
96
97    fn driver_name(&self) -> &'static str {
98        "rest-api"
99    }
100}