use std::cell::Cell;
use std::fmt;
use std::sync::Arc;
use serde_json::Value as JsonValue;
use crate::error::{Error, Result};
use crate::signature::Signature;
use crate::value::{Inspect, OUTER_WRAPPER, Value};
type Implementation =
dyn Fn(&[Option<JsonValue>], &HostCall<'_>) -> Result<Option<JsonValue>> + Send + Sync;
#[derive(Clone)]
pub struct HostFunction {
imp: Arc<Implementation>,
signature: Option<Arc<Signature>>,
arity: usize,
}
impl HostFunction {
pub fn new<F>(arity: usize, imp: F) -> Self
where
F: Fn(&[Option<JsonValue>], &HostCall<'_>) -> Result<Option<JsonValue>>
+ Send
+ Sync
+ 'static,
{
HostFunction {
imp: Arc::new(imp),
signature: None,
arity,
}
}
pub fn with_signature<F>(signature: &str, imp: F) -> Result<Self>
where
F: Fn(&[Option<JsonValue>], &HostCall<'_>) -> Result<Option<JsonValue>>
+ Send
+ Sync
+ 'static,
{
let signature = Signature::parse(signature)?;
Ok(HostFunction {
imp: Arc::new(imp),
arity: signature.params.len(),
signature: Some(Arc::new(signature)),
})
}
pub fn signature(&self) -> Option<&Signature> {
self.signature.as_deref()
}
pub fn arity(&self) -> usize {
self.arity
}
pub(crate) fn call<'a>(
&self,
args: &[Value<'a>],
input: &Value<'a>,
limit: usize,
read_failure: impl FnOnce() -> Result<()>,
) -> Result<Value<'a>> {
let too_large = || {
Error::new("U1001").with(
"message",
format!(
"The arguments of a host function would exceed the size limit of {limit} bytes"
),
)
};
let mut budget = limit;
if !args.iter().all(|arg| arg.json_fits(&mut budget)) {
return Err(too_large());
}
let json: Vec<Option<JsonValue>> = args.iter().map(Value::to_json).collect();
read_failure()?;
let input = match input {
Value::Array(a) if a.has(OUTER_WRAPPER) && a.len() == 1 => &a.items()[0],
v => v,
};
let call = HostCall {
input,
limit,
too_large: Cell::new(false),
};
let result = (self.imp)(&json, &call);
if call.too_large.get() {
return Err(too_large());
}
Ok(result?.as_ref().map_or(Value::Undefined, Value::from_json))
}
}
impl fmt::Debug for HostFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HostFunction")
.field(
"signature",
&self.signature.as_ref().map(|s| s.definition.as_str()),
)
.field("arity", &self.arity)
.finish_non_exhaustive()
}
}
pub struct HostCall<'a> {
input: &'a dyn Inspect,
limit: usize,
too_large: Cell<bool>,
}
impl HostCall<'_> {
pub fn input(&self) -> Option<JsonValue> {
self.input.to_json_within(self.limit).unwrap_or_else(|_| {
self.too_large.set(true);
None
})
}
}