1use std::{future::Future, pin::Pin, sync::Arc};
15
16use async_trait::async_trait;
17use ciborium::Value as CborValue;
18use indexmap::IndexMap;
19use vantage_core::Result;
20use vantage_dataset::im::{ImDataSource, ImTable};
21use vantage_dataset::traits::{ReadableValueSet, WritableValueSet};
22use vantage_types::{EmptyEntity, Record};
23
24use crate::{
25 capabilities::VistaCapabilities,
26 column::Column,
27 metadata::VistaMetadata,
28 reference::{ContainedKind, ContainedSpec, Reference},
29 source::TableShell,
30 vista::Vista,
31};
32
33pub type ContainedWriteback =
40 Arc<dyn Fn(CborValue) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
41
42pub type ContainedRefResolver =
46 Arc<dyn Fn(&str, &Record<CborValue>) -> Result<Vista> + Send + Sync>;
47
48pub struct ContainedShell {
55 im: ImTable<EmptyEntity, CborValue>,
56 metadata: VistaMetadata,
57 kind: ContainedKind,
58 id_column: Option<String>,
59 capabilities: VistaCapabilities,
60 writeback: ContainedWriteback,
61 ref_resolver: Option<ContainedRefResolver>,
62}
63
64pub fn build_contained_vista(
71 spec: &ContainedSpec,
72 host_value: Option<&CborValue>,
73 writeback: ContainedWriteback,
74 ref_resolver: Option<ContainedRefResolver>,
75) -> Result<Vista> {
76 let ds = ImDataSource::<CborValue>::new();
77 let im = ImTable::<EmptyEntity, CborValue>::new(&ds, &spec.name);
78 im.seed(seed_records(spec, host_value));
79
80 let mut metadata = VistaMetadata::new();
81 for column in &spec.columns {
82 metadata = metadata.with_column(column.clone());
83 }
84 if let Some(id) = &spec.id_column {
85 metadata = metadata.with_id_column(id.clone());
86 }
87
88 let shell = ContainedShell {
89 im,
90 metadata,
91 kind: spec.kind,
92 id_column: spec.id_column.clone(),
93 capabilities: VistaCapabilities {
94 can_count: true,
95 can_insert: true,
96 can_update: true,
97 can_delete: true,
98 ..VistaCapabilities::default()
99 },
100 writeback,
101 ref_resolver,
102 };
103 Ok(Vista::new(spec.name.clone(), Box::new(shell)))
104}
105
106fn seed_records(
108 spec: &ContainedSpec,
109 host_value: Option<&CborValue>,
110) -> IndexMap<String, Record<CborValue>> {
111 let mut rows = IndexMap::new();
112 match spec.kind {
113 ContainedKind::ContainsOne => {
114 if let Some(record) = host_value.and_then(map_to_record) {
115 rows.insert(ONE_ID.to_string(), record);
116 }
117 }
118 ContainedKind::ContainsMany => {
119 if let Some(CborValue::Array(items)) = host_value {
120 for (idx, item) in items.iter().enumerate() {
121 if let Some(record) = map_to_record(item) {
122 rows.insert(row_id(spec, &record, idx), record);
123 }
124 }
125 }
126 }
127 }
128 rows
129}
130
131impl ContainedShell {
132 async fn flush(&self) -> Result<()> {
135 let rows = self.im.list_values().await?;
136 let value = match self.kind {
137 ContainedKind::ContainsOne => rows
138 .into_values()
139 .next()
140 .map(CborValue::from)
141 .unwrap_or_else(|| CborValue::Map(Vec::new())),
142 ContainedKind::ContainsMany => {
143 CborValue::Array(rows.into_values().map(CborValue::from).collect())
144 }
145 };
146 (self.writeback)(value).await
147 }
148
149 async fn next_id(&self, record: &Record<CborValue>) -> Result<String> {
153 if let Some(id) = self.id_column.as_deref().and_then(|c| record.get(c)) {
154 return Ok(cbor_scalar_string(id));
155 }
156 match self.kind {
157 ContainedKind::ContainsOne => Ok(ONE_ID.to_string()),
158 ContainedKind::ContainsMany => Ok(self.im.list_values().await?.len().to_string()),
159 }
160 }
161}
162
163#[async_trait]
164impl TableShell for ContainedShell {
165 fn columns(&self) -> &IndexMap<String, Column> {
166 &self.metadata.columns
167 }
168
169 fn references(&self) -> &IndexMap<String, Reference> {
170 &self.metadata.references
171 }
172
173 fn id_column(&self) -> Option<&str> {
174 self.metadata.id_column.as_deref()
175 }
176
177 fn get_ref(&self, relation: &str, row: &Record<CborValue>) -> Result<Vista> {
180 match &self.ref_resolver {
181 Some(resolve) => resolve(relation, row),
182 None => Err(vantage_core::error!(
183 "contained record has no relation",
184 relation = relation
185 )),
186 }
187 }
188
189 async fn list_vista_values(
190 &self,
191 _vista: &Vista,
192 ) -> Result<IndexMap<String, Record<CborValue>>> {
193 self.im.list_values().await
194 }
195
196 async fn get_vista_value(
197 &self,
198 _vista: &Vista,
199 id: &String,
200 ) -> Result<Option<Record<CborValue>>> {
201 self.im.get_value(id).await
202 }
203
204 async fn get_vista_some_value(
205 &self,
206 _vista: &Vista,
207 ) -> Result<Option<(String, Record<CborValue>)>> {
208 self.im.get_some_value().await
209 }
210
211 async fn get_vista_count(&self, _vista: &Vista) -> Result<i64> {
212 Ok(self.im.list_values().await?.len() as i64)
213 }
214
215 async fn insert_vista_value(
216 &self,
217 _vista: &Vista,
218 id: &String,
219 record: &Record<CborValue>,
220 ) -> Result<Record<CborValue>> {
221 let stored = self.im.insert_value(id, record).await?;
222 self.flush().await?;
223 Ok(stored)
224 }
225
226 async fn insert_vista_return_id_value(
227 &self,
228 _vista: &Vista,
229 record: &Record<CborValue>,
230 ) -> Result<String> {
231 let id = self.next_id(record).await?;
232 self.im.insert_value(&id, record).await?;
233 self.flush().await?;
234 Ok(id)
235 }
236
237 async fn replace_vista_value(
238 &self,
239 _vista: &Vista,
240 id: &String,
241 record: &Record<CborValue>,
242 ) -> Result<Record<CborValue>> {
243 let stored = self.im.replace_value(id, record).await?;
244 self.flush().await?;
245 Ok(stored)
246 }
247
248 async fn patch_vista_value(
249 &self,
250 _vista: &Vista,
251 id: &String,
252 partial: &Record<CborValue>,
253 ) -> Result<Record<CborValue>> {
254 let stored = self.im.patch_value(id, partial).await?;
255 self.flush().await?;
256 Ok(stored)
257 }
258
259 async fn delete_vista_value(&self, _vista: &Vista, id: &String) -> Result<()> {
260 self.im.delete(id).await?;
261 self.flush().await
262 }
263
264 async fn delete_vista_all_values(&self, _vista: &Vista) -> Result<()> {
265 self.im.delete_all().await?;
266 self.flush().await
267 }
268
269 fn capabilities(&self) -> &VistaCapabilities {
270 &self.capabilities
271 }
272
273 fn driver_name(&self) -> &'static str {
274 "contained"
275 }
276}
277
278const ONE_ID: &str = "0";
280
281fn row_id(spec: &ContainedSpec, record: &Record<CborValue>, idx: usize) -> String {
282 spec.id_column
283 .as_deref()
284 .and_then(|c| record.get(c))
285 .map(cbor_scalar_string)
286 .unwrap_or_else(|| idx.to_string())
287}
288
289fn map_to_record(value: &CborValue) -> Option<Record<CborValue>> {
291 matches!(value, CborValue::Map(_)).then(|| Record::<CborValue>::from(value.clone()))
292}
293
294fn cbor_scalar_string(value: &CborValue) -> String {
296 match value {
297 CborValue::Text(s) => s.clone(),
298 CborValue::Integer(i) => i128::from(*i).to_string(),
299 other => format!("{other:?}"),
300 }
301}