use std::{future::Future, pin::Pin, sync::Arc};
use async_trait::async_trait;
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::Result;
use vantage_dataset::im::{ImDataSource, ImTable};
use vantage_dataset::traits::{ReadableValueSet, WritableValueSet};
use vantage_types::{EmptyEntity, Record};
use crate::{
capabilities::VistaCapabilities,
column::Column,
metadata::VistaMetadata,
reference::{ContainedKind, ContainedSpec, Reference},
source::TableShell,
vista::Vista,
};
pub type ContainedWriteback =
Arc<dyn Fn(CborValue) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
pub type ContainedRefResolver =
Arc<dyn Fn(&str, &Record<CborValue>) -> Result<Vista> + Send + Sync>;
pub struct ContainedShell {
im: ImTable<EmptyEntity, CborValue>,
metadata: VistaMetadata,
kind: ContainedKind,
id_column: Option<String>,
capabilities: VistaCapabilities,
writeback: ContainedWriteback,
ref_resolver: Option<ContainedRefResolver>,
}
pub fn build_contained_vista(
spec: &ContainedSpec,
host_value: Option<&CborValue>,
writeback: ContainedWriteback,
ref_resolver: Option<ContainedRefResolver>,
) -> Result<Vista> {
let ds = ImDataSource::<CborValue>::new();
let im = ImTable::<EmptyEntity, CborValue>::new(&ds, &spec.name);
im.seed(seed_records(spec, host_value));
let mut metadata = VistaMetadata::new();
for column in &spec.columns {
metadata = metadata.with_column(column.clone());
}
if let Some(id) = &spec.id_column {
metadata = metadata.with_id_column(id.clone());
}
let shell = ContainedShell {
im,
metadata,
kind: spec.kind,
id_column: spec.id_column.clone(),
capabilities: VistaCapabilities {
can_count: true,
can_insert: true,
can_update: true,
can_delete: true,
..VistaCapabilities::default()
},
writeback,
ref_resolver,
};
Ok(Vista::new(spec.name.clone(), Box::new(shell)))
}
fn seed_records(
spec: &ContainedSpec,
host_value: Option<&CborValue>,
) -> IndexMap<String, Record<CborValue>> {
let mut rows = IndexMap::new();
match spec.kind {
ContainedKind::ContainsOne => {
if let Some(record) = host_value.and_then(map_to_record) {
rows.insert(ONE_ID.to_string(), record);
}
}
ContainedKind::ContainsMany => {
if let Some(CborValue::Array(items)) = host_value {
for (idx, item) in items.iter().enumerate() {
if let Some(record) = map_to_record(item) {
rows.insert(row_id(spec, &record, idx), record);
}
}
}
}
}
rows
}
impl ContainedShell {
async fn flush(&self) -> Result<()> {
let rows = self.im.list_values().await?;
let value = match self.kind {
ContainedKind::ContainsOne => rows
.into_values()
.next()
.map(CborValue::from)
.unwrap_or_else(|| CborValue::Map(Vec::new())),
ContainedKind::ContainsMany => {
CborValue::Array(rows.into_values().map(CborValue::from).collect())
}
};
(self.writeback)(value).await
}
async fn next_id(&self, record: &Record<CborValue>) -> Result<String> {
if let Some(id) = self.id_column.as_deref().and_then(|c| record.get(c)) {
return Ok(cbor_scalar_string(id));
}
match self.kind {
ContainedKind::ContainsOne => Ok(ONE_ID.to_string()),
ContainedKind::ContainsMany => Ok(self.im.list_values().await?.len().to_string()),
}
}
}
#[async_trait]
impl TableShell for ContainedShell {
fn columns(&self) -> &IndexMap<String, Column> {
&self.metadata.columns
}
fn references(&self) -> &IndexMap<String, Reference> {
&self.metadata.references
}
fn id_column(&self) -> Option<&str> {
self.metadata.id_column.as_deref()
}
fn get_ref(&self, relation: &str, row: &Record<CborValue>) -> Result<Vista> {
match &self.ref_resolver {
Some(resolve) => resolve(relation, row),
None => Err(vantage_core::error!(
"contained record has no relation",
relation = relation
)),
}
}
async fn list_vista_values(
&self,
_vista: &Vista,
) -> Result<IndexMap<String, Record<CborValue>>> {
self.im.list_values().await
}
async fn get_vista_value(
&self,
_vista: &Vista,
id: &String,
) -> Result<Option<Record<CborValue>>> {
self.im.get_value(id).await
}
async fn get_vista_some_value(
&self,
_vista: &Vista,
) -> Result<Option<(String, Record<CborValue>)>> {
self.im.get_some_value().await
}
async fn get_vista_count(&self, _vista: &Vista) -> Result<i64> {
Ok(self.im.list_values().await?.len() as i64)
}
async fn insert_vista_value(
&self,
_vista: &Vista,
id: &String,
record: &Record<CborValue>,
) -> Result<Record<CborValue>> {
let stored = self.im.insert_value(id, record).await?;
self.flush().await?;
Ok(stored)
}
async fn insert_vista_return_id_value(
&self,
_vista: &Vista,
record: &Record<CborValue>,
) -> Result<String> {
let id = self.next_id(record).await?;
self.im.insert_value(&id, record).await?;
self.flush().await?;
Ok(id)
}
async fn replace_vista_value(
&self,
_vista: &Vista,
id: &String,
record: &Record<CborValue>,
) -> Result<Record<CborValue>> {
let stored = self.im.replace_value(id, record).await?;
self.flush().await?;
Ok(stored)
}
async fn patch_vista_value(
&self,
_vista: &Vista,
id: &String,
partial: &Record<CborValue>,
) -> Result<Record<CborValue>> {
let stored = self.im.patch_value(id, partial).await?;
self.flush().await?;
Ok(stored)
}
async fn delete_vista_value(&self, _vista: &Vista, id: &String) -> Result<()> {
self.im.delete(id).await?;
self.flush().await
}
async fn delete_vista_all_values(&self, _vista: &Vista) -> Result<()> {
self.im.delete_all().await?;
self.flush().await
}
fn capabilities(&self) -> &VistaCapabilities {
&self.capabilities
}
fn driver_name(&self) -> &'static str {
"contained"
}
}
const ONE_ID: &str = "0";
fn row_id(spec: &ContainedSpec, record: &Record<CborValue>, idx: usize) -> String {
spec.id_column
.as_deref()
.and_then(|c| record.get(c))
.map(cbor_scalar_string)
.unwrap_or_else(|| idx.to_string())
}
fn map_to_record(value: &CborValue) -> Option<Record<CborValue>> {
matches!(value, CborValue::Map(_)).then(|| Record::<CborValue>::from(value.clone()))
}
fn cbor_scalar_string(value: &CborValue) -> String {
match value {
CborValue::Text(s) => s.clone(),
CborValue::Integer(i) => i128::from(*i).to_string(),
other => format!("{other:?}"),
}
}