use std::sync::Arc;
use ciborium::Value as CborValue;
use vantage_core::error;
use vantage_dataset::{InsertableValueSet, WritableValueSet};
use vantage_rhai::rhai::{Dynamic, Engine, Map as RhaiMap};
use vantage_types::cbor_id_to_string;
use super::bridge::block_on;
use super::convert::map_to_record;
use super::handle::Handle;
use super::import::{import_rows, import_rows_mapped};
use super::read::{RhaiResult, id_string, rhai_err, run};
use super::vocab::Writes;
use crate::{ColumnFamily, ColumnFlag, VistaResolver, vista::Vista};
pub(crate) fn register_writes(
engine: &mut Engine,
resolver: Option<Arc<dyn VistaResolver>>,
writes: Writes,
) {
let gate = Gate::new(resolver, writes);
let w = gate.clone();
engine.register_fn(
"insert",
move |h: &mut Handle, map: RhaiMap| -> RhaiResult<String> {
insert_record(&w.target(h)?, map_to_record(map)?, None)
},
);
let w = gate.clone();
engine.register_fn(
"upsert",
move |h: &mut Handle, id: Dynamic, map: RhaiMap| -> RhaiResult<String> {
let vista = w.target(h)?;
let id = id_string(id)?;
run(vista.upsert_value(id.clone(), &map_to_record(map)?))?;
Ok(id)
},
);
let w = gate.clone();
engine.register_fn(
"patch",
move |h: &mut Handle, id: Dynamic, map: RhaiMap| -> RhaiResult<bool> {
let vista = w.target(h)?;
let rec = map_to_record(map)?;
found(block_on(vista.patch_value(id_string(id)?, &rec)))
},
);
let w = gate.clone();
engine.register_fn(
"delete",
move |h: &mut Handle, id: Dynamic| -> RhaiResult<bool> {
delete_row(&w.target(h)?, id_string(id)?)
},
);
let w = gate.clone();
engine.register_fn(
"import_from",
move |h: &mut Handle, source: Handle| -> RhaiResult<RhaiMap> {
import_rows(&w.target(h)?, &source, w.resolver())
},
);
let w = gate;
engine.register_fn(
"import_from",
move |h: &mut Handle, source: Handle, mapping: RhaiMap| -> RhaiResult<RhaiMap> {
import_rows_mapped(&w.target(h)?, &source, &mapping, w.resolver())
},
);
}
#[derive(Clone)]
pub(crate) struct Gate {
resolver: Option<Arc<dyn VistaResolver>>,
writes: Writes,
}
impl Gate {
pub(crate) fn new(resolver: Option<Arc<dyn VistaResolver>>, writes: Writes) -> Self {
Self { resolver, writes }
}
pub(crate) fn resolver(&self) -> Option<&dyn VistaResolver> {
self.resolver.as_deref()
}
pub(crate) fn target(&self, h: &Handle) -> RhaiResult<Vista> {
match &self.writes {
Writes::Denied(msg) => Err(msg.clone().into()),
Writes::Allowed => h.write_target(self.resolver()).map_err(rhai_err),
}
}
}
pub(crate) fn insert_record(
vista: &Vista,
mut rec: vantage_types::Record<CborValue>,
minted: Option<&str>,
) -> RhaiResult<String> {
let id_col = vista.id_column().unwrap_or("id").to_string();
let explicit = rec
.get(&id_col)
.filter(|v| !matches!(v, CborValue::Null))
.and_then(cbor_id_to_string);
let id = match explicit {
Some(id) => id,
None if has_auto_id(vista) => return run(vista.insert_return_id_value(&rec)),
None => match minted {
Some(id) => id.to_string(),
None => new_id(vista, &id_col)?,
},
};
rec.shift_remove(&id_col);
run(vista.insert_value(id.clone(), &rec))?;
Ok(id)
}
pub(crate) fn has_auto_id(vista: &Vista) -> bool {
vista
.id_column()
.and_then(|c| vista.column(c))
.is_some_and(|c| c.has_flag(ColumnFlag::Auto))
}
pub(crate) fn new_id(vista: &Vista, id_col: &str) -> RhaiResult<String> {
if let Some(column) = vista.column(id_col)
&& matches!(
column.family(),
Some(ColumnFamily::Integer | ColumnFamily::Float | ColumnFamily::Decimal)
)
{
return Err(rhai_err(error!(
"id column holds numbers: declare server-made ids (flag `auto`) or pass an id",
column = id_col,
r#type = column.original_type.as_deref().unwrap_or_default()
)));
}
Ok(uuid::Uuid::now_v7().to_string())
}
pub(crate) fn delete_row(vista: &Vista, id: String) -> RhaiResult<bool> {
run(WritableValueSet::delete(vista, id))?;
Ok(true)
}
pub(crate) fn found<T>(out: vantage_core::Result<vantage_core::Result<T>>) -> RhaiResult<bool> {
match out.and_then(|r| r) {
Ok(_) => Ok(true),
Err(e) if e.is_not_found() => Ok(false),
Err(e) => Err(rhai_err(e)),
}
}