jlens_core/host.rs
1//! Functions provided by the host application (see ADR 0006).
2
3use std::cell::Cell;
4use std::fmt;
5use std::sync::Arc;
6
7use serde_json::Value as JsonValue;
8
9use crate::error::Result;
10use crate::signature::Signature;
11use crate::value::{Inspect, OUTER_WRAPPER, TooLarge, Value};
12
13type Implementation =
14 dyn Fn(&[Option<JsonValue>], &HostCall<'_>) -> Result<Option<JsonValue>> + Send + Sync;
15
16/// A function implemented by the host application, bound to an expression through
17/// [`Bindings::function`](crate::Bindings::function) and called like a built-in.
18///
19/// Arguments and results cross the boundary as JSON: `None` stands for *undefined*, and a
20/// function passed as an argument arrives as `None`; [`HostCall::function`] gives a handle to
21/// call it (ADR 0011). An error returned by the function stops the evaluation; its position and
22/// token are set to the call site when it has none.
23///
24/// ```
25/// use jlens_core::{Bindings, Expression, HostFunction};
26/// use serde_json::json;
27///
28/// let tax = HostFunction::with_signature("<n-n?:n>", |args, _call| {
29/// let amount = args[0].as_ref().and_then(|v| v.as_f64()).unwrap_or(0.0);
30/// let rate = args[1].as_ref().and_then(|v| v.as_f64()).unwrap_or(0.25);
31/// Ok(Some(json!(amount * rate)))
32/// })
33/// .unwrap();
34/// let bindings = Bindings::new().function("tax", tax);
35/// // `price.$tax()` takes its amount from the context, as the `-` in the signature allows.
36/// let expr = Expression::compile("$tax(200) + price.$tax()").unwrap();
37/// let result = expr.evaluate(Some(&json!({"price": 8})), &bindings).unwrap();
38/// assert_eq!(result, Some(json!(52)));
39/// ```
40#[derive(Clone)]
41pub struct HostFunction {
42 imp: Arc<Implementation>,
43 signature: Option<Arc<Signature>>,
44 arity: usize,
45}
46
47impl HostFunction {
48 /// A function without a signature, declaring `arity` parameters.
49 ///
50 /// The arity plays the part of a JavaScript function's `length`. Higher-order functions use
51 /// it to decide which arguments to pass (`$map` passes the index only to functions of two
52 /// parameters or more), and partial application (`$f(?, 2)`) to know how many parameters
53 /// there are. Arguments are passed as given, however many the call has: index them with
54 /// `get`.
55 pub fn new<F>(arity: usize, imp: F) -> Self
56 where
57 F: Fn(&[Option<JsonValue>], &HostCall<'_>) -> Result<Option<JsonValue>>
58 + Send
59 + Sync
60 + 'static,
61 {
62 HostFunction {
63 imp: Arc::new(imp),
64 signature: None,
65 arity,
66 }
67 }
68
69 /// A function with a [signature](crate::signature) such as `<n-n?:n>`, which arguments are
70 /// checked against before each call, as for built-in functions (`T0410`, `T0412`). The
71 /// function then receives at least one argument per parameter in the signature (`None` for
72 /// a missing optional one), and the context value where a parameter has the `-` modifier.
73 /// The arity is the number of parameters in the signature.
74 ///
75 /// Fails with the signature's syntax error (`S0401`, `S0402`) if it is invalid.
76 pub fn with_signature<F>(signature: &str, imp: F) -> Result<Self>
77 where
78 F: Fn(&[Option<JsonValue>], &HostCall<'_>) -> Result<Option<JsonValue>>
79 + Send
80 + Sync
81 + 'static,
82 {
83 let signature = Signature::parse(signature)?;
84 Ok(HostFunction {
85 imp: Arc::new(imp),
86 arity: signature.params.len(),
87 signature: Some(Arc::new(signature)),
88 })
89 }
90
91 /// The signature, if the function has one.
92 pub fn signature(&self) -> Option<&Signature> {
93 self.signature.as_deref()
94 }
95
96 /// The number of declared parameters.
97 pub fn arity(&self) -> usize {
98 self.arity
99 }
100
101 /// Calls the function with evaluated arguments, converting them to JSON and the result back.
102 /// The arguments together, and the context value, are limited to `limit` bytes of JSON.
103 /// `caller` is the evaluator running the call, holding the same arguments.
104 pub(crate) fn call<'a>(
105 &self,
106 args: &[Value<'a>],
107 input: &Value<'a>,
108 limit: usize,
109 caller: &dyn Caller,
110 ) -> Result<Value<'a>> {
111 let too_large = |e: TooLarge| {
112 e.error(|| {
113 format!(
114 "The arguments of a host function would exceed the size limit of {limit} bytes"
115 )
116 })
117 };
118 let mut budget = limit;
119 let json = args
120 .iter()
121 .map(|arg| arg.to_json_spending(&mut budget, 0))
122 .collect::<std::result::Result<Vec<Option<JsonValue>>, _>>()
123 .map_err(too_large)?;
124 // Converting the arguments reads them: a part of the input that could not be read is
125 // not given to the function.
126 caller.read_failure()?;
127 let input = match input {
128 // The input array, wrapped so that it counts as a single context value.
129 Value::Array(a) if a.has(OUTER_WRAPPER) && a.len() == 1 => &a.items()[0],
130 v => v,
131 };
132 let call = HostCall {
133 input,
134 limit,
135 too_large: Cell::new(None),
136 caller,
137 };
138 let result = (self.imp)(&json, &call);
139 if let Some(e) = call.too_large.get() {
140 return Err(too_large(e));
141 }
142 Ok(result?.as_ref().map_or(Value::Undefined, Value::from_json))
143 }
144}
145
146impl fmt::Debug for HostFunction {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 f.debug_struct("HostFunction")
149 .field(
150 "signature",
151 &self.signature.as_ref().map(|s| s.definition.as_str()),
152 )
153 .field("arity", &self.arity)
154 .finish_non_exhaustive()
155 }
156}
157
158/// The evaluator running a host call, as the call sees it (ADR 0011). A trait object, so that
159/// [`HostCall`] keeps a single lifetime.
160pub(crate) trait Caller {
161 /// Fails once a level of the input could not be read from its source.
162 fn read_failure(&self) -> Result<()>;
163 /// The arity of argument `index`, when it is a function.
164 fn arity(&self, index: usize) -> Option<usize>;
165 /// Applies argument `index`, a function, to `args`.
166 fn apply(&self, index: usize, args: &[Option<JsonValue>]) -> Result<Option<JsonValue>>;
167}
168
169/// The context of a call to a [`HostFunction`].
170pub struct HostCall<'a> {
171 input: &'a dyn Inspect,
172 limit: usize,
173 /// Set when the context value was too large to convert; the call then fails with `U1001`.
174 too_large: Cell<Option<TooLarge>>,
175 caller: &'a dyn Caller,
176}
177
178impl HostCall<'_> {
179 /// The context value of the call: the input of the function call expression, such as the
180 /// current item in `items.$f()`. `None` when it is undefined.
181 ///
182 /// The value is converted to JSON when this method is called, so a function that does not
183 /// need it does not pay for converting it. A value larger than the size limit of a result
184 /// (`Options::max_result_size`), or nested more than 1,000 levels deep, is `None`, and the
185 /// evaluation then fails with `U1001`, whatever the function returns.
186 pub fn input(&self) -> Option<JsonValue> {
187 self.input.to_json_within(self.limit).unwrap_or_else(|e| {
188 self.too_large.set(Some(e));
189 None
190 })
191 }
192
193 /// A handle to call argument `index` when it is a function, such as the lambda in
194 /// `$retry(function() { $fetch() }, 3)`: a lambda, a built-in function, a partial
195 /// application, a chain or a host function. `None` when it is not a function. Arguments are
196 /// indexed as the function receives them, after the signature has been checked.
197 ///
198 /// The function still receives `None` in place of the argument, as ADR 0006 decided; the
199 /// handle borrows the call, so it cannot be called once the function has returned.
200 ///
201 /// ```
202 /// use jlens_core::{Bindings, Expression, HostFunction};
203 /// use serde_json::json;
204 ///
205 /// // `$twice($f, $x)` gives `$f($f($x))`.
206 /// let twice = HostFunction::with_signature("<fx:x>", |args, call| {
207 /// let f = call.function(0).expect("a function, as the signature requires");
208 /// let once = f.call(&[args[1].clone()])?;
209 /// f.call(&[once])
210 /// })
211 /// .unwrap();
212 /// let bindings = Bindings::new().function("twice", twice);
213 /// let expr = Expression::compile("$twice(function($n) { $n * 3 }, 2)").unwrap();
214 /// assert_eq!(expr.evaluate(None, &bindings).unwrap(), Some(json!(18)));
215 /// ```
216 pub fn function(&self, index: usize) -> Option<Callable<'_>> {
217 let arity = self.caller.arity(index)?;
218 Some(Callable {
219 caller: self.caller,
220 index,
221 arity,
222 })
223 }
224}
225
226/// A function passed to a [`HostFunction`], which it can call while it runs (ADR 0011). Given
227/// by [`HostCall::function`].
228#[derive(Clone, Copy)]
229pub struct Callable<'a> {
230 caller: &'a dyn Caller,
231 index: usize,
232 arity: usize,
233}
234
235impl Callable<'_> {
236 /// The number of declared parameters, as [`HostFunction::arity`] gives for a host function.
237 pub fn arity(&self) -> usize {
238 self.arity
239 }
240
241 /// Applies the function to `args`, as `$map` applies its function: extra arguments are
242 /// passed on, missing ones are undefined, and the context value is `null`. A lambda keeps
243 /// the variables of its definition.
244 ///
245 /// Arguments and the result cross the boundary as JSON, as for the host function itself: a
246 /// sequence comes back as an array or a single value, and a function as `None`.
247 ///
248 /// The call is part of the evaluation: it counts as a level of depth and checks the
249 /// timeout and the cancellation token, its result is limited like the evaluation's
250 /// (`Options::max_result_size`), and an audit shows its steps under the host function's
251 /// call. Its errors come back with their codes; returning one ends the evaluation, as any
252 /// error from the expression does.
253 pub fn call(&self, args: &[Option<JsonValue>]) -> Result<Option<JsonValue>> {
254 self.caller.apply(self.index, args)
255 }
256}
257
258impl fmt::Debug for Callable<'_> {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 f.debug_struct("Callable")
261 .field("index", &self.index)
262 .field("arity", &self.arity)
263 .finish_non_exhaustive()
264 }
265}