use std::sync::Arc;
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::{Result, error};
use vantage_types::Record;
use crate::{
capabilities::VistaCapabilities,
column::Column,
flags,
reference::{Reference, ReferenceKind},
sort::SortDirection,
source::TableShell,
};
pub type ForeignResolver = dyn Fn(&Record<CborValue>) -> Result<Vista> + Send + Sync;
pub struct ForeignRef {
pub kind: ReferenceKind,
pub resolver: Arc<ForeignResolver>,
}
impl std::fmt::Debug for ForeignRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ForeignRef")
.field("kind", &self.kind)
.finish_non_exhaustive()
}
}
pub struct Vista {
pub(crate) name: String,
pub(crate) foreign_resolvers: IndexMap<String, ForeignRef>,
pub(crate) capabilities: VistaCapabilities,
pub source: Box<dyn TableShell>,
}
impl Vista {
pub fn new(name: impl Into<String>, source: Box<dyn TableShell>) -> Self {
let capabilities = source.capabilities().clone();
Self {
name: name.into(),
foreign_resolvers: IndexMap::new(),
capabilities,
source,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = name.into();
}
pub fn capabilities(&self) -> &VistaCapabilities {
&self.capabilities
}
pub fn driver(&self) -> &'static str {
self.source.driver_name()
}
pub fn get_id_column(&self) -> Option<&str> {
self.source.id_column()
}
pub fn get_title_columns(&self) -> Vec<&str> {
self.source
.columns()
.values()
.filter(|c| c.is_title())
.map(|c| c.name.as_str())
.collect()
}
pub fn get_column_names(&self) -> Vec<&str> {
self.source.columns().keys().map(String::as_str).collect()
}
pub fn get_column(&self, name: &str) -> Option<&Column> {
self.source.columns().get(name)
}
pub fn get_references(&self) -> Vec<String> {
self.list_references().into_iter().map(|(n, _)| n).collect()
}
pub fn list_references(&self) -> Vec<(String, ReferenceKind)> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for (name, fref) in &self.foreign_resolvers {
if seen.insert(name.clone()) {
out.push((name.clone(), fref.kind));
}
}
for (name, kind) in self.source.get_ref_kinds() {
if seen.insert(name.clone()) {
out.push((name, kind));
}
}
out
}
pub fn get_reference(&self, name: &str) -> Option<&Reference> {
self.source.references().get(name)
}
pub fn add_condition_eq(&mut self, field: impl Into<String>, value: CborValue) -> Result<()> {
self.source.add_eq_condition(&field.into(), &value)
}
pub fn with_id(&mut self, id: impl Into<CborValue>) -> Result<&mut Self> {
let id_column = self
.get_id_column()
.ok_or_else(|| error!("vista has no id column"))?
.to_string();
self.add_condition_eq(id_column, id.into())?;
Ok(self)
}
pub async fn get_count(&self) -> Result<i64> {
self.source.get_vista_count(self).await
}
pub fn add_raw_condition<C: Send + Sync + 'static>(&mut self, condition: C) -> Result<()> {
self.source.add_raw_condition(Box::new(condition))
}
pub fn set_page_size(&mut self, size: usize) -> Result<()> {
self.source.set_page_size(size)
}
pub async fn fetch_page(&self, page: usize) -> Result<Vec<(String, Record<CborValue>)>> {
self.source.fetch_page(self, page).await
}
pub async fn fetch_next(
&self,
token: Option<CborValue>,
) -> Result<(Vec<(String, Record<CborValue>)>, Option<CborValue>)> {
self.source.fetch_next(self, token).await
}
pub fn add_search(&mut self, text: impl Into<String>) -> Result<()> {
self.source.add_search(&text.into())
}
pub fn clear_search(&mut self) -> Result<()> {
self.source.clear_search()
}
pub fn add_order(&mut self, column: &str, dir: SortDirection) -> Result<()> {
let col = self
.source
.columns()
.get(column)
.ok_or_else(|| error!("Unknown column for add_order", column = column))?;
if !col.has_flag(flags::ORDERABLE) {
return Err(error!(
format!("column '{}' is not orderable", column),
column = column
)
.is_unsupported());
}
self.source.add_order(column, dir)
}
pub fn clear_orders(&mut self) -> Result<()> {
self.source.clear_orders()
}
pub fn with_foreign(
&mut self,
relation: impl Into<String>,
kind: ReferenceKind,
resolver: impl Fn(&Record<CborValue>) -> Result<Vista> + Send + Sync + 'static,
) -> &mut Self {
self.foreign_resolvers.insert(
relation.into(),
ForeignRef {
kind,
resolver: Arc::new(resolver),
},
);
self
}
pub fn get_ref(&self, relation: &str, row: &Record<CborValue>) -> Result<Vista> {
if let Some(fref) = self.foreign_resolvers.get(relation) {
return (fref.resolver)(row);
}
self.source.get_ref(relation, row)
}
}