use std::sync::Arc;
use ciborium::Value as CborValue;
use vantage_core::{Result, VantageError};
use vantage_dataset::ReadableValueSet;
use vantage_rhai::rhai::{Array, Dynamic, Engine, EvalAltResult, Map as RhaiMap};
use vantage_types::Record;
use super::bridge::block_on;
use super::convert::record_to_dynamic;
use super::handle::Handle;
use super::introspect::{capabilities_map, columns_array, references_array};
use crate::{VistaResolver, vista::Vista};
pub(crate) type RhaiResult<T> = std::result::Result<T, Box<EvalAltResult>>;
pub(crate) fn register_reads(
engine: &mut Engine,
resolver: Option<Arc<dyn VistaResolver>>,
cap: Option<usize>,
) {
let r = resolver.clone();
engine.register_fn("list", move |h: &mut Handle| -> RhaiResult<Array> {
let (vista, rows) = rows(h, r.as_deref(), cap)?;
Ok(rows
.iter()
.map(|(id, rec)| row_dynamic(&vista, id, rec))
.collect())
});
let r = resolver.clone();
engine.register_fn("ids", move |h: &mut Handle| -> RhaiResult<Array> {
let (_, rows) = rows(h, r.as_deref(), cap)?;
Ok(rows.into_iter().map(|(id, _)| id.into()).collect())
});
let r = resolver.clone();
engine.register_fn("first", move |h: &mut Handle| -> RhaiResult<Dynamic> {
let (vista, rows) = rows(h, r.as_deref(), Some(1))?;
Ok(rows
.first()
.map_or(Dynamic::UNIT, |(id, rec)| row_dynamic(&vista, id, rec)))
});
let r = resolver.clone();
engine.register_fn(
"get",
move |h: &mut Handle, id: Dynamic| -> RhaiResult<Dynamic> {
let id = id_string(id)?;
let vista = h.resolve(r.as_deref()).map_err(rhai_err)?;
Ok(match run(vista.get_value(id.clone()))? {
Some(rec) => row_dynamic(&vista, &id, &rec),
None => Dynamic::UNIT,
})
},
);
let r = resolver.clone();
engine.register_fn("count", move |h: &mut Handle| -> RhaiResult<i64> {
let vista = h.resolve(r.as_deref()).map_err(rhai_err)?;
let count = run(vista.count())?;
Ok(h.row_limit().map_or(count, |n| count.min(n as i64)))
});
let r = resolver.clone();
engine.register_fn(
"capabilities",
move |h: &mut Handle| -> RhaiResult<RhaiMap> {
Ok(capabilities_map(
&h.resolve(r.as_deref()).map_err(rhai_err)?,
))
},
);
let r = resolver.clone();
engine.register_fn("columns", move |h: &mut Handle| -> RhaiResult<Array> {
Ok(columns_array(&h.resolve(r.as_deref()).map_err(rhai_err)?))
});
let r = resolver;
engine.register_fn("references", move |h: &mut Handle| -> RhaiResult<Array> {
Ok(references_array(
&h.resolve(r.as_deref()).map_err(rhai_err)?,
))
});
}
type Rows = Vec<(String, Record<CborValue>)>;
fn rows(
h: &Handle,
resolver: Option<&dyn VistaResolver>,
cap: Option<usize>,
) -> RhaiResult<(Vista, Rows)> {
let vista = h.resolve(resolver).map_err(rhai_err)?;
let limit = match (cap, h.row_limit()) {
(Some(a), Some(b)) => Some(a.min(b)),
(a, b) => a.or(b),
};
let rows = run(fetch_rows(&vista, limit))?;
Ok((vista, rows))
}
pub(crate) async fn fetch_rows(vista: &Vista, limit: Option<usize>) -> Result<Rows> {
let Some(limit) = limit else {
return Ok(vista.list_values().await?.into_iter().collect());
};
let mut rows = vista.fetch_window(0, limit).await?.rows;
rows.truncate(limit);
Ok(rows)
}
pub(crate) fn row_dynamic(vista: &Vista, id: &str, rec: &Record<CborValue>) -> Dynamic {
let col = vista.id_column().unwrap_or("id");
if rec.contains_key(col) {
return record_to_dynamic(rec);
}
let mut rec = rec.clone();
rec.insert(col.to_string(), CborValue::Text(id.to_string()));
record_to_dynamic(&rec)
}
pub(crate) fn run<T>(fut: impl Future<Output = Result<T>>) -> RhaiResult<T> {
block_on(fut).and_then(|r| r).map_err(rhai_err)
}
pub(crate) fn id_string(id: Dynamic) -> RhaiResult<String> {
if id.is_string() {
return Ok(id.into_string().expect("string"));
}
if id.is_int() {
return Ok(id.as_int().expect("int").to_string());
}
Err(format!(
"an id must be a string or an integer, not {}",
id.type_name()
)
.into())
}
pub(crate) fn rhai_err(e: VantageError) -> Box<EvalAltResult> {
e.to_string().into()
}