vantage_api_client/vista/
source.rs1use std::sync::Arc;
18
19use async_trait::async_trait;
20use ciborium::Value as CborValue;
21use indexmap::IndexMap;
22use vantage_core::{Result, error};
23use vantage_expressions::Expression;
24use vantage_expressions::traits::expressive::{DeferredFn, ExpressiveEnum};
25use vantage_table::traits::table_like::TableLike;
26use vantage_types::Record;
27use vantage_vista::{TableShell, Vista, VistaCapabilities};
28
29use crate::vista::factory::ModelResolver;
30
31use super::any_shell::AnyTableShell;
32
33#[derive(Clone)]
38pub(crate) struct YamlReference {
39 pub target: String,
40 pub kind: YamlReferenceKind,
41 pub foreign_key: String,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub(crate) enum YamlReferenceKind {
46 HasMany,
47 HasOne,
48}
49
50pub struct RestApiTableShell {
51 pub(crate) table: Box<dyn TableLike<Value = CborValue, Id = String>>,
52 pub(crate) capabilities: VistaCapabilities,
53 pub(crate) yaml_refs: IndexMap<String, YamlReference>,
54 pub(crate) resolver: Option<ModelResolver>,
55}
56
57impl RestApiTableShell {
58 pub(crate) fn new(
59 table: Box<dyn TableLike<Value = CborValue, Id = String>>,
60 capabilities: VistaCapabilities,
61 ) -> Self {
62 Self {
63 table,
64 capabilities,
65 yaml_refs: IndexMap::new(),
66 resolver: None,
67 }
68 }
69
70 pub(crate) fn with_yaml_refs(mut self, refs: IndexMap<String, YamlReference>) -> Self {
71 self.yaml_refs = refs;
72 self
73 }
74
75 pub(crate) fn with_resolver(mut self, resolver: ModelResolver) -> Self {
76 self.resolver = Some(resolver);
77 self
78 }
79
80 fn build_deferred_fk_condition(
87 parent_table: Box<dyn TableLike<Value = CborValue, Id = String>>,
88 source_column: String,
89 target_field: String,
90 ) -> Expression<CborValue> {
91 let parent_arc = Arc::new(parent_table);
92 let column = source_column;
93 let target_field_for_error = target_field.clone();
94 let deferred = DeferredFn::new(move || {
95 let parent = parent_arc.clone_box();
96 let column = column.clone();
97 let target = target_field_for_error.clone();
98 Box::pin(async move {
99 let records = parent.list_values().await?;
100 let value = records
101 .values()
102 .next()
103 .and_then(|r| r.get(&column))
104 .cloned()
105 .ok_or_else(|| {
106 error!(
107 "YAML reference: parent yielded no row or column missing",
108 source_column = column,
109 target_field = target
110 )
111 })?;
112 Ok(ExpressiveEnum::Scalar(value))
113 })
114 });
115
116 Expression::new(
117 "{} = {}",
118 vec![
119 ExpressiveEnum::Nested(Expression::new(target_field, vec![])),
120 ExpressiveEnum::Nested(Expression::new(
121 "{}",
122 vec![ExpressiveEnum::Deferred(deferred)],
123 )),
124 ],
125 )
126 }
127}
128
129#[async_trait]
130impl TableShell for RestApiTableShell {
131 async fn list_vista_values(
132 &self,
133 _vista: &Vista,
134 ) -> Result<IndexMap<String, Record<CborValue>>> {
135 self.table.list_values().await
136 }
137
138 async fn get_vista_value(
139 &self,
140 _vista: &Vista,
141 id: &String,
142 ) -> Result<Option<Record<CborValue>>> {
143 let mut data = self.table.list_values().await?;
144 Ok(data.shift_remove(id))
145 }
146
147 async fn get_vista_some_value(
148 &self,
149 _vista: &Vista,
150 ) -> Result<Option<(String, Record<CborValue>)>> {
151 let data = self.table.list_values().await?;
152 Ok(data.into_iter().next())
153 }
154
155 async fn get_vista_count(&self, _vista: &Vista) -> Result<i64> {
156 self.table.get_count().await
157 }
158
159 fn add_eq_condition(&mut self, field: &str, value: &CborValue) -> Result<()> {
160 let condition = crate::eq_condition(field, value.clone());
164 self.table.add_condition(Box::new(condition))
165 }
166
167 fn add_raw_condition(&mut self, condition: Box<dyn std::any::Any + Send + Sync>) -> Result<()> {
168 self.table.add_condition(condition)
169 }
170
171 fn get_ref(&self, relation: &str) -> Result<Vista> {
172 if let Some(yref) = self.yaml_refs.get(relation) {
177 let resolver = self.resolver.as_ref().ok_or_else(|| {
178 error!(
179 "YAML reference requires a model resolver — call \
180 `with_model_resolver` on the factory or register \
181 the target spec via `register_yaml`",
182 relation = relation
183 )
184 })?;
185
186 let mut child = resolver(&yref.target)?;
187
188 let (source_column, target_field) = match yref.kind {
193 YamlReferenceKind::HasMany => {
194 let parent_id = self.table.id_field_name().ok_or_else(|| {
195 error!(
196 "YAML has_many reference needs the parent to have an id field",
197 relation = relation
198 )
199 })?;
200 (parent_id, yref.foreign_key.clone())
201 }
202 YamlReferenceKind::HasOne => {
203 let child_id = child
204 .get_id_column()
205 .map(str::to_string)
206 .unwrap_or_else(|| "id".to_string());
207 (yref.foreign_key.clone(), child_id)
208 }
209 };
210
211 let parent_clone = self.table.clone_box();
212 let condition =
213 Self::build_deferred_fk_condition(parent_clone, source_column, target_field);
214 child.add_raw_condition(condition)?;
215
216 return Ok(child);
217 }
218
219 let any_table = self.table.get_ref(relation)?;
223 AnyTableShell::into_vista(any_table)
224 }
225
226 fn capabilities(&self) -> &VistaCapabilities {
227 &self.capabilities
228 }
229
230 fn driver_name(&self) -> &'static str {
231 "rest-api"
232 }
233}