vrl/compiler/
function.rs

1#![allow(clippy::missing_errors_doc)]
2pub mod closure;
3
4use crate::diagnostic::{DiagnosticMessage, Label, Note};
5use crate::parser::ast::Ident;
6use crate::path::OwnedTargetPath;
7use crate::value::{KeyString, Value, kind::Collection};
8use std::{
9    collections::{BTreeMap, HashMap},
10    fmt,
11};
12
13use super::{
14    CompileConfig, Span, TypeDef,
15    expression::{Block, Container, Expr, Expression, container::Variant},
16    state::TypeState,
17    value::{Kind, kind},
18};
19
20pub type Compiled = Result<Box<dyn Expression>, Box<dyn DiagnosticMessage>>;
21pub type CompiledArgument =
22    Result<Option<Box<dyn std::any::Any + Send + Sync>>, Box<dyn DiagnosticMessage>>;
23
24pub trait Function: Send + Sync + fmt::Debug {
25    /// The identifier by which the function can be called.
26    fn identifier(&self) -> &'static str;
27
28    /// A brief single-line description explaining what this function does.
29    fn summary(&self) -> &'static str {
30        "TODO"
31    }
32
33    /// A more elaborate multi-paragraph description on how to use the function.
34    fn usage(&self) -> &'static str {
35        "TODO"
36    }
37
38    /// One or more examples demonstrating usage of the function in VRL source
39    /// code.
40    fn examples(&self) -> &'static [Example];
41
42    /// Compile a [`Function`] into a type that can be resolved to an
43    /// [`Expression`].
44    ///
45    /// This function is called at compile-time for any `Function` used in the
46    /// program.
47    ///
48    /// At runtime, the `Expression` returned by this function is executed and
49    /// resolved to its final [`Value`].
50    fn compile(
51        &self,
52        state: &TypeState,
53        ctx: &mut FunctionCompileContext,
54        arguments: ArgumentList,
55    ) -> Compiled;
56
57    /// An optional list of parameters the function accepts.
58    ///
59    /// This list is used at compile-time to check function arity, keyword names
60    /// and argument type definition.
61    fn parameters(&self) -> &'static [Parameter] {
62        &[]
63    }
64
65    /// An optional closure definition for the function.
66    ///
67    /// This returns `None` by default, indicating the function doesn't accept
68    /// a closure.
69    fn closure(&self) -> Option<closure::Definition> {
70        None
71    }
72}
73
74// -----------------------------------------------------------------------------
75
76#[derive(Debug, Copy, Clone, PartialEq, Eq)]
77pub struct Example {
78    pub title: &'static str,
79    pub source: &'static str,
80    pub result: Result<&'static str, &'static str>,
81}
82
83// This type is re-exposed so renaming it is a breaking change.
84#[allow(clippy::module_name_repetitions)]
85pub struct FunctionCompileContext {
86    span: Span,
87    config: CompileConfig,
88}
89
90impl FunctionCompileContext {
91    #[must_use]
92    pub fn new(span: Span, config: CompileConfig) -> Self {
93        Self { span, config }
94    }
95
96    /// Span information for the function call.
97    #[must_use]
98    pub fn span(&self) -> Span {
99        self.span
100    }
101
102    /// Get an immutable reference to a stored external context, if one exists.
103    #[must_use]
104    pub fn get_external_context<T: 'static>(&self) -> Option<&T> {
105        self.config.get_custom()
106    }
107
108    /// Get a mutable reference to a stored external context, if one exists.
109    pub fn get_external_context_mut<T: 'static>(&mut self) -> Option<&mut T> {
110        self.config.get_custom_mut()
111    }
112
113    #[must_use]
114    pub fn is_read_only_path(&self, path: &OwnedTargetPath) -> bool {
115        self.config.is_read_only_path(path)
116    }
117
118    /// Consume the `FunctionCompileContext`, returning the (potentially mutated) `AnyMap`.
119    #[must_use]
120    pub fn into_config(self) -> CompileConfig {
121        self.config
122    }
123}
124
125// -----------------------------------------------------------------------------
126
127#[derive(Debug, Copy, Clone, PartialEq, Eq)]
128pub struct Parameter {
129    /// The keyword of the parameter.
130    ///
131    /// Arguments can be passed in both using the keyword, or as a positional
132    /// argument.
133    pub keyword: &'static str,
134
135    /// The type kind(s) this parameter expects to receive.
136    ///
137    /// If an invalid kind is provided, the compiler will return a compile-time
138    /// error.
139    pub kind: u16,
140
141    /// Whether or not this is a required parameter.
142    ///
143    /// If it isn't, the function can be called without errors, even if the
144    /// argument matching this parameter is missing.
145    pub required: bool,
146}
147
148impl Parameter {
149    #[allow(arithmetic_overflow)]
150    #[must_use]
151    pub fn kind(&self) -> Kind {
152        let mut kind = Kind::never();
153
154        let n = self.kind;
155
156        if (n & kind::BYTES) == kind::BYTES {
157            kind.add_bytes();
158        }
159
160        if (n & kind::INTEGER) == kind::INTEGER {
161            kind.add_integer();
162        }
163
164        if (n & kind::FLOAT) == kind::FLOAT {
165            kind.add_float();
166        }
167
168        if (n & kind::BOOLEAN) == kind::BOOLEAN {
169            kind.add_boolean();
170        }
171
172        if (n & kind::OBJECT) == kind::OBJECT {
173            kind.add_object(Collection::any());
174        }
175
176        if (n & kind::ARRAY) == kind::ARRAY {
177            kind.add_array(Collection::any());
178        }
179
180        if (n & kind::TIMESTAMP) == kind::TIMESTAMP {
181            kind.add_timestamp();
182        }
183
184        if (n & kind::REGEX) == kind::REGEX {
185            kind.add_regex();
186        }
187
188        if (n & kind::NULL) == kind::NULL {
189            kind.add_null();
190        }
191
192        if (n & kind::UNDEFINED) == kind::UNDEFINED {
193            kind.add_undefined();
194        }
195
196        kind
197    }
198}
199
200// -----------------------------------------------------------------------------
201
202#[derive(Debug, Default, Clone)]
203pub struct ArgumentList {
204    pub(crate) arguments: HashMap<&'static str, Expr>,
205
206    /// A closure argument differs from regular arguments, in that it isn't an
207    /// expression by itself, and it also isn't tied to a parameter string in
208    /// the function call.
209    ///
210    /// We do still want to store the closure in the argument list, to allow
211    /// function implementors access to the closure through `Function::compile`.
212    closure: Option<Closure>,
213}
214
215impl ArgumentList {
216    #[must_use]
217    pub fn optional(&self, keyword: &'static str) -> Option<Box<dyn Expression>> {
218        self.optional_expr(keyword).map(|v| Box::new(v) as _)
219    }
220
221    #[must_use]
222    pub fn required(&self, keyword: &'static str) -> Box<dyn Expression> {
223        Box::new(self.required_expr(keyword)) as _
224    }
225
226    pub fn optional_literal(
227        &self,
228        keyword: &'static str,
229        state: &TypeState,
230    ) -> Result<Option<Value>, Error> {
231        self.optional_expr(keyword)
232            .map(|expr| match expr.resolve_constant(state) {
233                Some(value) => Ok(value),
234                _ => Err(Error::UnexpectedExpression {
235                    keyword,
236                    expected: "literal",
237                    expr,
238                }),
239            })
240            .transpose()
241    }
242
243    pub fn required_literal(
244        &self,
245        keyword: &'static str,
246        state: &TypeState,
247    ) -> Result<Value, Error> {
248        Ok(required(self.optional_literal(keyword, state)?))
249    }
250
251    pub fn optional_enum(
252        &self,
253        keyword: &'static str,
254        variants: &[Value],
255        state: &TypeState,
256    ) -> Result<Option<Value>, Error> {
257        self.optional_literal(keyword, state)?
258            .map(|value| {
259                variants
260                    .iter()
261                    .find(|v| *v == &value)
262                    .cloned()
263                    .ok_or(Error::InvalidEnumVariant {
264                        keyword,
265                        value,
266                        variants: variants.to_vec(),
267                    })
268            })
269            .transpose()
270    }
271
272    pub fn required_enum(
273        &self,
274        keyword: &'static str,
275        variants: &[Value],
276        state: &TypeState,
277    ) -> Result<Value, Error> {
278        Ok(required(self.optional_enum(keyword, variants, state)?))
279    }
280
281    pub fn optional_query(
282        &self,
283        keyword: &'static str,
284    ) -> Result<Option<crate::compiler::expression::Query>, Error> {
285        self.optional_expr(keyword)
286            .map(|expr| match expr {
287                Expr::Query(query) => Ok(query),
288                expr => Err(Error::UnexpectedExpression {
289                    keyword,
290                    expected: "query",
291                    expr,
292                }),
293            })
294            .transpose()
295    }
296
297    pub fn required_query(
298        &self,
299        keyword: &'static str,
300    ) -> Result<crate::compiler::expression::Query, Error> {
301        Ok(required(self.optional_query(keyword)?))
302    }
303
304    pub fn optional_regex(
305        &self,
306        keyword: &'static str,
307        state: &TypeState,
308    ) -> Result<Option<regex::Regex>, Error> {
309        self.optional_expr(keyword)
310            .map(|expr| match expr.resolve_constant(state) {
311                Some(Value::Regex(regex)) => Ok((*regex).clone()),
312                _ => Err(Error::UnexpectedExpression {
313                    keyword,
314                    expected: "regex",
315                    expr,
316                }),
317            })
318            .transpose()
319    }
320
321    pub fn required_regex(
322        &self,
323        keyword: &'static str,
324        state: &TypeState,
325    ) -> Result<regex::Regex, Error> {
326        Ok(required(self.optional_regex(keyword, state)?))
327    }
328
329    pub fn optional_object(
330        &self,
331        keyword: &'static str,
332    ) -> Result<Option<BTreeMap<KeyString, Expr>>, Error> {
333        self.optional_expr(keyword)
334            .map(|expr| match expr {
335                Expr::Container(Container {
336                    variant: Variant::Object(object),
337                }) => Ok((*object).clone()),
338                expr => Err(Error::UnexpectedExpression {
339                    keyword,
340                    expected: "object",
341                    expr,
342                }),
343            })
344            .transpose()
345    }
346
347    pub fn required_object(
348        &self,
349        keyword: &'static str,
350    ) -> Result<BTreeMap<KeyString, Expr>, Error> {
351        Ok(required(self.optional_object(keyword)?))
352    }
353
354    pub fn optional_array(&self, keyword: &'static str) -> Result<Option<Vec<Expr>>, Error> {
355        self.optional_expr(keyword)
356            .map(|expr| match expr {
357                Expr::Container(Container {
358                    variant: Variant::Array(array),
359                }) => Ok((*array).clone()),
360                expr => Err(Error::UnexpectedExpression {
361                    keyword,
362                    expected: "array",
363                    expr,
364                }),
365            })
366            .transpose()
367    }
368
369    pub fn required_array(&self, keyword: &'static str) -> Result<Vec<Expr>, Error> {
370        Ok(required(self.optional_array(keyword)?))
371    }
372
373    #[must_use]
374    pub fn optional_closure(&self) -> Option<&Closure> {
375        self.closure.as_ref()
376    }
377
378    pub fn required_closure(&self) -> Result<Closure, Error> {
379        self.optional_closure()
380            .cloned()
381            .ok_or(Error::ExpectedFunctionClosure)
382    }
383
384    pub(crate) fn keywords(&self) -> Vec<&'static str> {
385        self.arguments.keys().copied().collect::<Vec<_>>()
386    }
387
388    pub(crate) fn insert(&mut self, k: &'static str, v: Expr) {
389        self.arguments.insert(k, v);
390    }
391
392    pub(crate) fn set_closure(&mut self, closure: Closure) {
393        self.closure = Some(closure);
394    }
395
396    pub(crate) fn optional_expr(&self, keyword: &'static str) -> Option<Expr> {
397        self.arguments.get(keyword).cloned()
398    }
399
400    #[must_use]
401    pub fn required_expr(&self, keyword: &'static str) -> Expr {
402        required(self.optional_expr(keyword))
403    }
404}
405
406fn required<T>(argument: Option<T>) -> T {
407    argument.expect("invalid function signature")
408}
409
410#[cfg(any(test, feature = "test"))]
411mod test_impls {
412    use super::{ArgumentList, HashMap, Span, Value};
413    use crate::compiler::expression::FunctionArgument;
414    use crate::compiler::parser::Node;
415
416    impl From<HashMap<&'static str, Value>> for ArgumentList {
417        fn from(map: HashMap<&'static str, Value>) -> Self {
418            Self {
419                arguments: map
420                    .into_iter()
421                    .map(|(k, v)| (k, v.into()))
422                    .collect::<HashMap<_, _>>(),
423                closure: None,
424            }
425        }
426    }
427
428    impl From<ArgumentList> for Vec<(&'static str, Option<FunctionArgument>)> {
429        fn from(args: ArgumentList) -> Self {
430            args.arguments
431                .iter()
432                .map(|(key, expr)| {
433                    (
434                        *key,
435                        Some(FunctionArgument::new(
436                            None,
437                            Node::new(Span::default(), expr.clone()),
438                        )),
439                    )
440                })
441                .collect()
442        }
443    }
444}
445
446// -----------------------------------------------------------------------------
447
448#[derive(Debug, Clone, PartialEq)]
449pub struct Closure {
450    pub variables: Vec<Ident>,
451    pub block: Block,
452    pub block_type_def: TypeDef,
453}
454
455impl Closure {
456    #[must_use]
457    pub fn new<T: Into<Ident>>(variables: Vec<T>, block: Block, block_type_def: TypeDef) -> Self {
458        Self {
459            variables: variables.into_iter().map(Into::into).collect(),
460            block,
461            block_type_def,
462        }
463    }
464}
465
466// -----------------------------------------------------------------------------
467
468#[derive(thiserror::Error, Debug, PartialEq)]
469pub enum Error {
470    #[error("unexpected expression type")]
471    UnexpectedExpression {
472        keyword: &'static str,
473        expected: &'static str,
474        expr: Expr,
475    },
476
477    #[error(r#"invalid enum variant""#)]
478    InvalidEnumVariant {
479        keyword: &'static str,
480        value: Value,
481        variants: Vec<Value>,
482    },
483
484    #[error("this argument must be a static expression")]
485    ExpectedStaticExpression { keyword: &'static str, expr: Expr },
486
487    #[error("invalid argument")]
488    InvalidArgument {
489        keyword: &'static str,
490        value: Value,
491        error: &'static str,
492    },
493
494    #[error("missing function closure")]
495    ExpectedFunctionClosure,
496
497    #[error("mutation of read-only value")]
498    ReadOnlyMutation { context: String },
499}
500
501impl crate::diagnostic::DiagnosticMessage for Error {
502    fn code(&self) -> usize {
503        use Error::{
504            ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
505            ReadOnlyMutation, UnexpectedExpression,
506        };
507
508        match self {
509            UnexpectedExpression { .. } => 400,
510            InvalidEnumVariant { .. } => 401,
511            ExpectedStaticExpression { .. } => 402,
512            InvalidArgument { .. } => 403,
513            ExpectedFunctionClosure => 420,
514            ReadOnlyMutation { .. } => 315,
515        }
516    }
517
518    fn labels(&self) -> Vec<Label> {
519        use Error::{
520            ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
521            ReadOnlyMutation, UnexpectedExpression,
522        };
523
524        match self {
525            UnexpectedExpression {
526                keyword,
527                expected,
528                expr,
529            } => vec![
530                Label::primary(
531                    format!(r#"unexpected expression for argument "{keyword}""#),
532                    Span::default(),
533                ),
534                Label::context(format!("received: {}", expr.as_str()), Span::default()),
535                Label::context(format!("expected: {expected}"), Span::default()),
536            ],
537
538            InvalidEnumVariant {
539                keyword,
540                value,
541                variants,
542            } => vec![
543                Label::primary(
544                    format!(r#"invalid enum variant for argument "{keyword}""#),
545                    Span::default(),
546                ),
547                Label::context(format!("received: {value}"), Span::default()),
548                Label::context(
549                    format!(
550                        "expected one of: {}",
551                        variants
552                            .iter()
553                            .map(std::string::ToString::to_string)
554                            .collect::<Vec<_>>()
555                            .join(", ")
556                    ),
557                    Span::default(),
558                ),
559            ],
560
561            ExpectedStaticExpression { keyword, expr } => vec![
562                Label::primary(
563                    format!(r#"expected static expression for argument "{keyword}""#),
564                    Span::default(),
565                ),
566                Label::context(format!("received: {}", expr.as_str()), Span::default()),
567            ],
568
569            InvalidArgument {
570                keyword,
571                value,
572                error,
573            } => vec![
574                Label::primary(format!(r#"invalid argument "{keyword}""#), Span::default()),
575                Label::context(format!("received: {value}"), Span::default()),
576                Label::context(format!("error: {error}"), Span::default()),
577            ],
578
579            ExpectedFunctionClosure => vec![],
580            ReadOnlyMutation { context } => vec![
581                Label::primary("mutation of read-only value", Span::default()),
582                Label::context(context, Span::default()),
583            ],
584        }
585    }
586
587    fn notes(&self) -> Vec<Note> {
588        vec![Note::SeeCodeDocs(self.code())]
589    }
590}
591
592impl From<Error> for Box<dyn crate::diagnostic::DiagnosticMessage> {
593    fn from(error: Error) -> Self {
594        Box::new(error) as _
595    }
596}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601
602    #[test]
603    fn test_parameter_kind() {
604        struct TestCase {
605            parameter_kind: u16,
606            kind: Kind,
607        }
608
609        for (
610            title,
611            TestCase {
612                parameter_kind,
613                kind,
614            },
615        ) in HashMap::from([
616            (
617                "bytes",
618                TestCase {
619                    parameter_kind: kind::BYTES,
620                    kind: Kind::bytes(),
621                },
622            ),
623            (
624                "integer",
625                TestCase {
626                    parameter_kind: kind::INTEGER,
627                    kind: Kind::integer(),
628                },
629            ),
630        ]) {
631            let parameter = Parameter {
632                keyword: "",
633                kind: parameter_kind,
634                required: false,
635            };
636
637            assert_eq!(parameter.kind(), kind, "{title}");
638        }
639    }
640}