Skip to main content

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