use ciborium::Value as CborValue;
use vantage_core::error;
use vantage_dataset::{InsertableValueSet, WritableValueSet};
use vantage_rhai::rhai::{Dynamic, Engine, EvalAltResult, Map as RhaiMap};
use vantage_types::cbor_id_to_string;
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, unsupported};
use super::vocab::{TargetResolver, Writes};
use crate::vista::Vista;
pub(crate) fn register_writes(
engine: &mut Engine,
resolver: Option<TargetResolver>,
writes: Writes,
) {
match writes {
Writes::Allowed => register_allowed(engine, resolver),
Writes::Denied(msg) => register_denied(engine, msg),
}
}
fn register_allowed(engine: &mut Engine, resolver: Option<TargetResolver>) {
let r = resolver.clone();
engine.register_fn(
"insert",
move |h: &mut Handle, map: RhaiMap| -> RhaiResult<String> {
let vista = target(h, r.as_ref(), "insert", |c| c.can_insert)?;
insert_record(&vista, map_to_record(map)?, None)
},
);
let r = resolver.clone();
engine.register_fn(
"upsert",
move |h: &mut Handle, id: Dynamic, map: RhaiMap| -> RhaiResult<String> {
let vista = target(h, r.as_ref(), "upsert", |c| c.can_insert && c.can_update)?;
let id = id_string(id)?;
run(vista.upsert_value(id.clone(), &map_to_record(map)?))?;
Ok(id)
},
);
let r = resolver.clone();
engine.register_fn(
"patch",
move |h: &mut Handle, id: Dynamic, map: RhaiMap| -> RhaiResult<bool> {
let vista = target(h, r.as_ref(), "patch", |c| c.can_update)?;
let rec = map_to_record(map)?;
found(super::bridge::block_on(
vista.patch_value(id_string(id)?, &rec),
))
},
);
let r = resolver.clone();
engine.register_fn(
"delete",
move |h: &mut Handle, id: Dynamic| -> RhaiResult<bool> {
let vista = h.write_target(r.as_ref()).map_err(rhai_err)?;
delete_row(&vista, id_string(id)?)
},
);
let r = resolver.clone();
engine.register_fn(
"import_from",
move |h: &mut Handle, source: Handle| -> RhaiResult<RhaiMap> {
import_rows(&import_target(h, r.as_ref())?, &source, r.as_ref())
},
);
let r = resolver;
engine.register_fn(
"import_from",
move |h: &mut Handle, source: Handle, mapping: RhaiMap| -> RhaiResult<RhaiMap> {
import_rows_mapped(
&import_target(h, r.as_ref())?,
&source,
&mapping,
r.as_ref(),
)
},
);
}
fn register_denied(engine: &mut Engine, msg: String) {
let deny = move || -> Box<EvalAltResult> { msg.clone().into() };
let d = deny.clone();
engine.register_fn(
"insert",
move |_: &mut Handle, _: RhaiMap| -> RhaiResult<String> { Err(d()) },
);
let d = deny.clone();
engine.register_fn(
"upsert",
move |_: &mut Handle, _: Dynamic, _: RhaiMap| -> RhaiResult<String> { Err(d()) },
);
let d = deny.clone();
engine.register_fn(
"patch",
move |_: &mut Handle, _: Dynamic, _: RhaiMap| -> RhaiResult<bool> { Err(d()) },
);
engine.register_fn("delete", |_: &mut Handle, _: Dynamic| -> RhaiResult<bool> {
Ok(false)
});
let d = deny.clone();
engine.register_fn(
"import_from",
move |_: &mut Handle, _: Handle| -> RhaiResult<RhaiMap> { Err(d()) },
);
engine.register_fn(
"import_from",
move |_: &mut Handle, _: Handle, _: RhaiMap| -> RhaiResult<RhaiMap> { Err(deny()) },
);
}
pub(crate) fn target(
h: &Handle,
resolver: Option<&TargetResolver>,
verb: &str,
allowed: impl Fn(&crate::VistaCapabilities) -> bool,
) -> RhaiResult<Vista> {
let vista = h.write_target(resolver).map_err(rhai_err)?;
if !allowed(vista.capabilities()) {
return Err(unsupported(verb, &vista));
}
Ok(vista)
}
fn import_target(h: &Handle, resolver: Option<&TargetResolver>) -> RhaiResult<Vista> {
target(h, resolver, "import_from", |c| c.can_import || c.can_insert)
}
pub(crate) fn insert_record(
vista: &Vista,
mut rec: vantage_types::Record<CborValue>,
minted: Option<&str>,
) -> RhaiResult<String> {
let id_col = vista.get_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 vista.has_auto_id() => 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 new_id(vista: &Vista, id_col: &str) -> RhaiResult<String> {
if let Some(column) = vista.get_column(id_col)
&& is_numeric_type(&column.original_type)
{
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_str()
)));
}
Ok(uuid::Uuid::now_v7().to_string())
}
fn is_numeric_type(t: &str) -> bool {
let t = t.trim().to_ascii_lowercase();
let base = t.split('(').next().unwrap_or("");
matches!(
base,
"int"
| "integer"
| "i8"
| "i16"
| "i32"
| "i64"
| "u8"
| "u16"
| "u32"
| "u64"
| "bigint"
| "smallint"
| "serial"
| "bigserial"
| "number"
| "numeric"
| "decimal"
| "float"
| "f32"
| "f64"
| "double"
| "real"
)
}
pub(crate) fn delete_row(vista: &Vista, id: String) -> RhaiResult<bool> {
if !vista.capabilities().can_delete {
return Ok(false);
}
match super::bridge::block_on(WritableValueSet::delete(vista, id)).and_then(|r| r) {
Ok(()) => Ok(true),
Err(e) if e.is_unsupported() => Ok(false),
Err(e) => Err(rhai_err(e)),
}
}
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)),
}
}