use std::cell::Cell;
use std::fmt;
use std::sync::Arc;
use serde_json::Value as JsonValue;
use crate::error::Result;
use crate::signature::Signature;
use crate::value::{Inspect, OUTER_WRAPPER, TooLarge, 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,
reads_input: bool,
}
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,
reads_input: true,
}
}
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)),
reads_input: true,
})
}
pub fn without_input(mut self) -> Self {
self.reads_input = false;
self
}
pub fn reads_input(&self) -> bool {
self.reads_input
}
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,
caller: &dyn Caller,
) -> Result<Value<'a>> {
let too_large = |e: TooLarge| {
e.error(|| {
format!(
"The arguments of a host function would exceed the size limit of {limit} bytes"
)
})
};
let mut budget = limit;
let json = args
.iter()
.map(|arg| arg.to_json_spending(&mut budget, 0))
.collect::<std::result::Result<Vec<Option<JsonValue>>, _>>()
.map_err(too_large)?;
caller.read_failure()?;
let undefined = Value::Undefined;
let input = match input {
_ if !self.reads_input => &undefined,
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(None),
caller,
};
let result = (self.imp)(&json, &call);
if let Some(e) = call.too_large.get() {
return Err(too_large(e));
}
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)
.field("reads_input", &self.reads_input)
.finish_non_exhaustive()
}
}
pub(crate) trait Caller {
fn read_failure(&self) -> Result<()>;
fn arity(&self, index: usize) -> Option<usize>;
fn apply(&self, index: usize, args: &[Option<JsonValue>]) -> Result<Option<JsonValue>>;
}
pub struct HostCall<'a> {
input: &'a dyn Inspect,
limit: usize,
too_large: Cell<Option<TooLarge>>,
caller: &'a dyn Caller,
}
impl HostCall<'_> {
pub fn input(&self) -> Option<JsonValue> {
self.input.to_json_within(self.limit).unwrap_or_else(|e| {
self.too_large.set(Some(e));
None
})
}
pub fn function(&self, index: usize) -> Option<Callable<'_>> {
let arity = self.caller.arity(index)?;
Some(Callable {
caller: self.caller,
index,
arity,
})
}
}
#[derive(Clone, Copy)]
pub struct Callable<'a> {
caller: &'a dyn Caller,
index: usize,
arity: usize,
}
impl Callable<'_> {
pub fn arity(&self) -> usize {
self.arity
}
pub fn call(&self, args: &[Option<JsonValue>]) -> Result<Option<JsonValue>> {
self.caller.apply(self.index, args)
}
}
impl fmt::Debug for Callable<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Callable")
.field("index", &self.index)
.field("arity", &self.arity)
.finish_non_exhaustive()
}
}