hcl/eval/
func.rs

1use crate::Value;
2use std::fmt;
3use std::iter;
4use std::ops;
5use std::slice;
6
7/// A type alias for the signature of functions expected by the [`FuncDef`] type.
8pub type Func = fn(FuncArgs) -> Result<Value, String>;
9
10/// A type hint for a function parameter.
11///
12/// The parameter type is used to validate the arguments of a function call expression before
13/// evaluating the function.
14///
15/// See the [documentation of `FuncDef`][FuncDef] for usage examples.
16#[derive(Debug, Clone)]
17pub enum ParamType {
18    /// Any type is allowed.
19    Any,
20    /// The parameter must be a boolean value.
21    Bool,
22    /// The parameter must be a number.
23    Number,
24    /// The parameter must be a string value.
25    String,
26    /// The parameter must be an array which must contain only elements of the given element type.
27    Array(Box<ParamType>),
28    /// The parameter must be an object which must contain only entries with values of the given
29    /// element type. The object key type is always a string.
30    Object(Box<ParamType>),
31    /// The parameter can be one of the provided types. If the `Vec` is empty, any type is
32    /// allowed.
33    OneOf(Vec<ParamType>),
34    /// The parameter must be either `null` or of the provided type.
35    Nullable(Box<ParamType>),
36}
37
38impl ParamType {
39    /// Creates a new `Array` parameter type with the given element type.
40    pub fn array_of(element: ParamType) -> Self {
41        ParamType::Array(Box::new(element))
42    }
43
44    /// Creates a new `Object` parameter type with the given element type.
45    ///
46    /// The object key type is always a string and thus not specified here.
47    pub fn object_of(element: ParamType) -> Self {
48        ParamType::Object(Box::new(element))
49    }
50
51    /// Creates a new `OneOf` parameter type from the provided alternatives.
52    pub fn one_of<I>(alternatives: I) -> Self
53    where
54        I: IntoIterator<Item = ParamType>,
55    {
56        ParamType::OneOf(alternatives.into_iter().collect())
57    }
58
59    /// Creates a new `Nullable` parameter type from a non-null parameter type.
60    pub fn nullable(non_null: ParamType) -> Self {
61        ParamType::Nullable(Box::new(non_null))
62    }
63
64    /// Tests the given value against the parameter type.
65    pub(super) fn is_satisfied_by(&self, value: &Value) -> bool {
66        match self {
67            ParamType::Any => true,
68            ParamType::Bool => value.is_boolean(),
69            ParamType::Number => value.is_number(),
70            ParamType::String => value.is_string(),
71            ParamType::Array(elem_type) => value
72                .as_array()
73                .is_some_and(|array| array.iter().all(|elem| elem_type.is_satisfied_by(elem))),
74            ParamType::Object(elem_type) => value
75                .as_object()
76                .is_some_and(|object| object.values().all(|elem| elem_type.is_satisfied_by(elem))),
77            ParamType::Nullable(elem_type) => value.is_null() || elem_type.is_satisfied_by(value),
78            ParamType::OneOf(elem_types) => elem_types
79                .iter()
80                .any(|elem_type| elem_type.is_satisfied_by(value)),
81        }
82    }
83}
84
85impl fmt::Display for ParamType {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            ParamType::Any => f.write_str("`any`"),
89            ParamType::Bool => f.write_str("`bool`"),
90            ParamType::Number => f.write_str("`number`"),
91            ParamType::String => f.write_str("`string`"),
92            ParamType::Array(elem_type) => write!(f, "`array({elem_type})`"),
93            ParamType::Object(elem_type) => write!(f, "`object({elem_type})`"),
94            ParamType::Nullable(elem_type) => write!(f, "`nullable({elem_type})`"),
95            ParamType::OneOf(elem_types) => match elem_types.len() {
96                0 => f.write_str("`any`"),
97                1 => fmt::Display::fmt(&elem_types[0], f),
98                n => {
99                    for (i, elem_type) in elem_types.iter().enumerate() {
100                        if i == n - 1 {
101                            f.write_str(" or ")?;
102                        } else if i > 0 {
103                            f.write_str(", ")?;
104                        }
105
106                        fmt::Display::fmt(elem_type, f)?;
107                    }
108                    Ok(())
109                }
110            },
111        }
112    }
113}
114
115/// The definition of a function that can be called in HCL expressions.
116///
117/// It defines the function to call, and number and types of parameters that the function accepts.
118/// The parameter information is used to validate function arguments prior to calling it.
119///
120/// The signature of a function is defined by the [`Func`] type alias. For available parameter
121/// types see the documentation of [`ParamType`].
122///
123/// # Function call evaluation
124///
125/// When a [`FuncCall`][crate::expr::FuncCall] is evaluated (via its
126/// [`evaluate`][crate::eval::Evaluate::evaluate] method), the arguments are validated against the
127/// defined function parameters before calling the function. The evaluation will stop with an error
128/// if too few or too many arguments are provided, of if their types do not match the expected
129/// parameter types.
130///
131/// Because all arguments are validated before calling the function, unnecessary length and
132/// type checks on the function arguments can be avoided in the function body.
133///
134/// # Examples
135///
136/// ```
137/// use hcl::eval::{Context, FuncArgs, FuncDef, ParamType};
138/// use hcl::Value;
139///
140/// fn add(args: FuncArgs) -> Result<Value, String> {
141///     let a = args[0].as_number().unwrap();
142///     let b = args[1].as_number().unwrap();
143///     Ok(Value::Number(*a + *b))
144/// }
145///
146/// let params = [ParamType::Number, ParamType::Number];
147///
148/// let func_def = FuncDef::new(add, params);
149///
150/// let mut ctx = Context::new();
151///
152/// // Declare the function in the context to make it available
153/// // during expression evaluation.
154/// ctx.declare_func("add", func_def);
155///
156/// // Use the context to evaluate an expression.
157/// // ...
158/// ```
159///
160/// Alternatively, the [`FuncDefBuilder`] can be used to construct the `FuncDef`:
161///
162/// ```
163/// # use hcl::eval::{FuncArgs, FuncDef, ParamType};
164/// # use hcl::Value;
165/// # fn add(args: FuncArgs) -> Result<Value, String> {
166/// #    unimplemented!()
167/// # }
168/// let func_def = FuncDef::builder()
169///     .param(ParamType::Number)
170///     .param(ParamType::Number)
171///     .build(add);
172/// ```
173///
174/// See the documentation of the [`FuncDefBuilder`] for all available methods.
175#[derive(Debug, Clone)]
176pub struct FuncDef {
177    func: Func,
178    params: Vec<ParamType>,
179    variadic_param: Option<ParamType>,
180}
181
182impl FuncDef {
183    /// Creates a new `FuncDef` from a function and its parameters.
184    ///
185    /// **Note**: if you want to define a `FuncDef` with a variadic parameter, use the
186    /// [`.builder()`] method. It provides a [`FuncDefBuilder`] which also lets you define
187    /// variadic parameters.
188    ///
189    /// See the type-level documentation of [`FuncDef`] for usage examples.
190    ///
191    /// [`.builder()`]: FuncDef::builder
192    pub fn new<P>(func: Func, params: P) -> FuncDef
193    where
194        P: IntoIterator<Item = ParamType>,
195    {
196        FuncDef::builder().params(params).build(func)
197    }
198
199    /// Creates a [`FuncDefBuilder`].
200    ///
201    /// See the type-level documentation of [`FuncDef`] for usage examples.
202    pub fn builder() -> FuncDefBuilder {
203        FuncDefBuilder {
204            params: Vec::new(),
205            variadic_param: None,
206        }
207    }
208
209    /// Calls the function with the provided arguments.
210    pub(super) fn call(&self, args: Vec<Value>) -> Result<Value, String> {
211        let params_len = self.params.len();
212        let args_len = args.len();
213
214        if args_len < params_len || (self.variadic_param.is_none() && args_len > params_len) {
215            return Err(format!(
216                "expected {params_len} positional arguments, got {args_len}"
217            ));
218        }
219
220        let (pos_args, var_args) = args.split_at(params_len);
221
222        for (pos, (arg, param)) in pos_args.iter().zip(self.params.iter()).enumerate() {
223            if !param.is_satisfied_by(arg) {
224                return Err(format!(
225                    "expected argument at position {pos} to be of type {param}, got `{arg}`",
226                ));
227            }
228        }
229
230        if let Some(var_param) = &self.variadic_param {
231            for (pos, arg) in var_args.iter().enumerate() {
232                if !var_param.is_satisfied_by(arg) {
233                    return Err(format!(
234                        "expected variadic argument at position {} to be of type {}, got `{}`",
235                        params_len + pos,
236                        var_param,
237                        arg
238                    ));
239                }
240            }
241        }
242
243        let func_args = FuncArgs::new(args, params_len);
244
245        (self.func)(func_args)
246    }
247}
248
249/// A builder for [`FuncDef`] values.
250///
251/// The builder is created by the [`.builder()`] method of `FuncDef`.
252///
253/// See the type-level documentation of [`FuncDef`] and builder method docs for usage examples.
254///
255/// [`.builder()`]: FuncDef::builder
256#[derive(Debug)]
257pub struct FuncDefBuilder {
258    params: Vec<ParamType>,
259    variadic_param: Option<ParamType>,
260}
261
262impl FuncDefBuilder {
263    /// Adds a function parameter.
264    ///
265    /// Calls to `.param()` and [`.params()`] can be mixed and will always add more parameters to
266    /// the function definition instead of overwriting existing ones.
267    ///
268    /// [`.params()`]: FuncDefBuilder::params
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// # use hcl::eval::{FuncArgs, FuncDef, ParamType};
274    /// # use hcl::Value;
275    /// # fn strlen(_: FuncArgs) -> Result<Value, String> {
276    /// #     unimplemented!()
277    /// # }
278    /// let func_def = FuncDef::builder()
279    ///     .param(ParamType::String)
280    ///     .build(strlen);
281    /// ```
282    pub fn param(mut self, param: ParamType) -> FuncDefBuilder {
283        self.params.push(param);
284        self
285    }
286
287    /// Adds function parameters from an iterator.
288    ///
289    /// Calls to `.params()` and [`.param()`] can be mixed and will always add more parameters to
290    /// the function definition instead of overwriting existing ones.
291    ///
292    /// [`.param()`]: FuncDefBuilder::param
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// # use hcl::eval::{FuncArgs, FuncDef, ParamType};
298    /// # use hcl::Value;
299    /// # fn add3(_: FuncArgs) -> Result<Value, String> {
300    /// #     unimplemented!()
301    /// # }
302    /// let func_def = FuncDef::builder()
303    ///     .params([
304    ///         ParamType::Number,
305    ///         ParamType::Number,
306    ///         ParamType::Number,
307    ///     ])
308    ///     .build(add3);
309    /// ```
310    pub fn params<I>(mut self, params: I) -> FuncDefBuilder
311    where
312        I: IntoIterator<Item = ParamType>,
313    {
314        self.params.extend(params);
315        self
316    }
317
318    /// Adds a variadic parameter to the function definition.
319    ///
320    /// Only one variadic parameter can be added. Subsequent invocation of this method will
321    /// overwrite a previously set variadic parameter.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// # use hcl::eval::{FuncArgs, FuncDef, ParamType};
327    /// # use hcl::Value;
328    /// # fn printf(_: FuncArgs) -> Result<Value, String> {
329    /// #     unimplemented!()
330    /// # }
331    /// let func_def = FuncDef::builder()
332    ///     .param(ParamType::String)
333    ///     .variadic_param(ParamType::Any)
334    ///     .build(printf);
335    /// ```
336    pub fn variadic_param(mut self, param: ParamType) -> FuncDefBuilder {
337        self.variadic_param = Some(param);
338        self
339    }
340
341    /// Takes ownership of the builder and builds the `FuncDef` for the provided function and the
342    /// contents of the builder.
343    pub fn build(self, func: Func) -> FuncDef {
344        FuncDef {
345            func,
346            params: self.params,
347            variadic_param: self.variadic_param,
348        }
349    }
350}
351
352/// Wrapper type for function argument values.
353///
354/// During expression evaluation it is passed to functions referenced by function call
355/// expressions with the values of the evaluated argument expressions.
356///
357/// `FuncArgs` behaves exactly like a `Vec<Value>` due to its `Deref` implementation, but exposes
358/// additional methods to iterate over positional and variadic arguments.
359#[derive(Debug, Clone)]
360pub struct FuncArgs {
361    values: Vec<Value>,
362    pos_args_len: usize,
363}
364
365impl FuncArgs {
366    pub(super) fn new(values: Vec<Value>, pos_args_len: usize) -> FuncArgs {
367        FuncArgs {
368            values,
369            pos_args_len,
370        }
371    }
372
373    /// Takes ownership of the function argument values.
374    pub fn into_values(self) -> Vec<Value> {
375        self.values
376    }
377
378    /// Returns an iterator over all positional arguments.
379    pub fn positional_args(&self) -> PositionalArgs<'_> {
380        PositionalArgs {
381            iter: self.values.iter().take(self.pos_args_len),
382        }
383    }
384
385    /// Returns an iterator over all variadic arguments.
386    pub fn variadic_args(&self) -> VariadicArgs<'_> {
387        VariadicArgs {
388            iter: self.values.iter().skip(self.pos_args_len),
389        }
390    }
391}
392
393impl ops::Deref for FuncArgs {
394    type Target = Vec<Value>;
395
396    fn deref(&self) -> &Self::Target {
397        &self.values
398    }
399}
400
401/// An iterator over positional function arguments.
402///
403/// This `struct` is created by the [`positional_args`] method on [`FuncArgs`]. See its
404/// documentation for more.
405///
406/// [`positional_args`]: FuncArgs::positional_args
407#[derive(Debug, Clone)]
408pub struct PositionalArgs<'a> {
409    iter: iter::Take<slice::Iter<'a, Value>>,
410}
411
412impl<'a> Iterator for PositionalArgs<'a> {
413    type Item = &'a Value;
414
415    fn next(&mut self) -> Option<Self::Item> {
416        self.iter.next()
417    }
418}
419
420/// An iterator over variadic function arguments.
421///
422/// This `struct` is created by the [`variadic_args`] method on [`FuncArgs`]. See its
423/// documentation for more.
424///
425/// [`variadic_args`]: FuncArgs::variadic_args
426#[derive(Debug, Clone)]
427pub struct VariadicArgs<'a> {
428    iter: iter::Skip<slice::Iter<'a, Value>>,
429}
430
431impl<'a> Iterator for VariadicArgs<'a> {
432    type Item = &'a Value;
433
434    fn next(&mut self) -> Option<Self::Item> {
435        self.iter.next()
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn param_type() {
445        let string = Value::from("a string");
446        let number = Value::from(42);
447        let boolean = Value::from(true);
448        let string_array = Value::from_iter(["foo", "bar"]);
449        let number_array = Value::from_iter([1, 2, 3]);
450        let object_of_strings = Value::from_iter([("foo", "bar"), ("baz", "qux")]);
451        let object_of_numbers = Value::from_iter([("foo", 1), ("bar", 2)]);
452
453        let param = ParamType::String;
454        assert!(param.is_satisfied_by(&string));
455        assert!(!param.is_satisfied_by(&number));
456
457        let param = ParamType::Any;
458        assert!(param.is_satisfied_by(&string));
459        assert!(param.is_satisfied_by(&number));
460
461        let param = ParamType::nullable(ParamType::String);
462        assert!(param.is_satisfied_by(&string));
463        assert!(param.is_satisfied_by(&Value::Null));
464        assert!(!param.is_satisfied_by(&number));
465
466        let param = ParamType::one_of([ParamType::String, ParamType::Number]);
467        assert!(param.is_satisfied_by(&string));
468        assert!(param.is_satisfied_by(&number));
469        assert!(!param.is_satisfied_by(&boolean));
470
471        let param = ParamType::array_of(ParamType::String);
472        assert!(param.is_satisfied_by(&string_array));
473        assert!(!param.is_satisfied_by(&number_array));
474
475        let param = ParamType::object_of(ParamType::String);
476        assert!(param.is_satisfied_by(&object_of_strings));
477        assert!(!param.is_satisfied_by(&object_of_numbers));
478    }
479}