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}