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
36    /// The category this function belongs to.
37    ///
38    /// This categorizes functions for documentation and tooling purposes.
39    fn category(&self) -> &'static str;
40
41    /// Human-readable internal failure reasons for the function.
42    ///
43    /// This returns an empty slice by default, indicating no internal failure
44    /// reasons are documented for a function.
45    fn internal_failure_reasons(&self) -> &'static [&'static str] {
46        &[]
47    }
48
49    /// The return type kind(s) this function can return.
50    fn return_kind(&self) -> u16;
51
52    /// Human-readable rules describing the return value of the function.
53    ///
54    /// This returns an empty slice by default, indicating no return rules
55    /// are documented for this function.
56    fn return_rules(&self) -> &'static [&'static str] {
57        &[]
58    }
59
60    /// Important notices about the function's behavior or usage.
61    ///
62    /// This returns an empty slice by default, indicating no notices
63    /// are documented for this function.
64    fn notices(&self) -> &'static [&'static str] {
65        &[]
66    }
67
68    /// Whether a function is pure or not. When a function is pure, it is
69    /// idempotent and has no side-effects. Otherwise, it is impure.
70    fn pure(&self) -> bool {
71        true
72    }
73
74    /// One or more examples demonstrating usage of the function in VRL source
75    /// code.
76    fn examples(&self) -> &'static [Example];
77
78    /// Compile a [`Function`] into a type that can be resolved to an
79    /// [`Expression`].
80    ///
81    /// This function is called at compile-time for any `Function` used in the
82    /// program.
83    ///
84    /// At runtime, the `Expression` returned by this function is executed and
85    /// resolved to its final [`Value`].
86    fn compile(
87        &self,
88        state: &TypeState,
89        ctx: &mut FunctionCompileContext,
90        arguments: ArgumentList,
91    ) -> Compiled;
92
93    /// An optional list of parameters the function accepts.
94    ///
95    /// This list is used at compile-time to check function arity, keyword names
96    /// and argument type definition.
97    fn parameters(&self) -> &'static [Parameter] {
98        &[]
99    }
100
101    /// An optional closure definition for the function.
102    ///
103    /// This returns `None` by default, indicating the function doesn't accept
104    /// a closure.
105    fn closure(&self) -> Option<closure::Definition> {
106        None
107    }
108}
109
110// -----------------------------------------------------------------------------
111
112#[derive(Debug, Copy, Clone, PartialEq, Eq)]
113pub struct Example {
114    pub title: &'static str,
115    pub source: &'static str,
116    pub input: Option<&'static str>,
117    pub result: Result<&'static str, &'static str>,
118    pub file: &'static str,
119    pub line: u32,
120    /// Whether this example produces deterministic output.
121    /// When false, tests validate output type instead of exact value.
122    pub deterministic: bool,
123}
124
125/// Macro to create an Example with automatic source location tracking
126#[macro_export]
127macro_rules! example {
128    (
129        title: $title:expr,
130        source: $source:expr,
131        input: $input:expr,
132        result: $result:expr $(,)?
133    ) => {
134        $crate::compiler::function::Example {
135            title: $title,
136            source: $source,
137            input: Some($input),
138            result: $result,
139            file: file!(),
140            line: line!(),
141            deterministic: true,
142        }
143    };
144    (
145        title: $title:expr,
146        source: $source:expr,
147        result: $result:expr $(,)?
148    ) => {
149        $crate::compiler::function::Example {
150            title: $title,
151            source: $source,
152            input: None,
153            result: $result,
154            file: file!(),
155            line: line!(),
156            deterministic: true,
157        }
158    };
159    (
160        title: $title:expr,
161        source: $source:expr,
162        result: $result:expr,
163        deterministic: $det:expr $(,)?
164    ) => {
165        $crate::compiler::function::Example {
166            title: $title,
167            source: $source,
168            input: None,
169            result: $result,
170            file: file!(),
171            line: line!(),
172            deterministic: $det,
173        }
174    };
175}
176
177// This type is re-exposed so renaming it is a breaking change.
178#[allow(clippy::module_name_repetitions)]
179pub struct FunctionCompileContext {
180    span: Span,
181    config: CompileConfig,
182}
183
184impl FunctionCompileContext {
185    #[must_use]
186    pub fn new(span: Span, config: CompileConfig) -> Self {
187        Self { span, config }
188    }
189
190    /// Span information for the function call.
191    #[must_use]
192    pub fn span(&self) -> Span {
193        self.span
194    }
195
196    /// Get an immutable reference to a stored external context, if one exists.
197    #[must_use]
198    pub fn get_external_context<T: 'static>(&self) -> Option<&T> {
199        self.config.get_custom()
200    }
201
202    /// Get a mutable reference to a stored external context, if one exists.
203    pub fn get_external_context_mut<T: 'static>(&mut self) -> Option<&mut T> {
204        self.config.get_custom_mut()
205    }
206
207    #[must_use]
208    pub fn is_read_only_path(&self, path: &OwnedTargetPath) -> bool {
209        self.config.is_read_only_path(path)
210    }
211
212    /// Consume the `FunctionCompileContext`, returning the (potentially mutated) `AnyMap`.
213    #[must_use]
214    pub fn into_config(self) -> CompileConfig {
215        self.config
216    }
217}
218
219// -----------------------------------------------------------------------------
220
221#[derive(Debug, Copy, Clone, PartialEq, Eq)]
222pub struct EnumVariant {
223    pub value: &'static str,
224    pub description: &'static str,
225}
226
227#[derive(Debug, Copy, Clone, PartialEq, Eq)]
228pub struct Parameter {
229    /// The keyword of the parameter.
230    ///
231    /// Arguments can be passed in both using the keyword, or as a positional
232    /// argument.
233    pub keyword: &'static str,
234
235    /// The type kind(s) this parameter expects to receive.
236    ///
237    /// If an invalid kind is provided, the compiler will return a compile-time
238    /// error.
239    pub kind: u16,
240
241    /// Whether or not this is a required parameter.
242    ///
243    /// If it isn't, the function can be called without errors, even if the
244    /// argument matching this parameter is missing.
245    pub required: bool,
246
247    /// A description of what this parameter does.
248    pub description: &'static str,
249
250    /// The default value for this parameter, if any.
251    ///
252    /// Notes on creating a `Option<&'static Value>`:
253    ///
254    /// * If the inner [`Value`] is copiable, such as [`Value::Integer`], you likely won't have issues.
255    /// * If the value can contain owned data, such as [`Value::Bytes`], use [`LazyLock`](std::sync::LazyLock)
256    ///   to create static [`Value`] instances. If you are already in a [`LazyLock`](std::sync::LazyLock) block,
257    ///   you'll have to create another [`LazyLock`](std::sync::LazyLock) in order to make both static.
258    pub default: Option<&'static Value>,
259
260    /// Enum variants for this parameter, if the parameter accepts a limited set of values.
261    pub enum_variants: Option<&'static [EnumVariant]>,
262}
263
264impl Parameter {
265    /// Create a required parameter with default values for `default` and `enum_variants`.
266    #[must_use]
267    pub const fn required(keyword: &'static str, kind: u16, description: &'static str) -> Self {
268        Self {
269            keyword,
270            kind,
271            required: true,
272            description,
273            default: None,
274            enum_variants: None,
275        }
276    }
277
278    /// Create an optional parameter with default values for `default` and `enum_variants`.
279    #[must_use]
280    pub const fn optional(keyword: &'static str, kind: u16, description: &'static str) -> Self {
281        Self {
282            keyword,
283            kind,
284            required: false,
285            description,
286            default: None,
287            enum_variants: None,
288        }
289    }
290
291    /// Set the default value for this parameter.
292    #[must_use]
293    pub const fn default(mut self, value: &'static Value) -> Self {
294        self.default = Some(value);
295        self
296    }
297
298    /// Set the enum variants for this parameter.
299    #[must_use]
300    pub const fn enum_variants(mut self, variants: &'static [EnumVariant]) -> Self {
301        self.enum_variants = Some(variants);
302        self
303    }
304
305    #[allow(arithmetic_overflow)]
306    #[must_use]
307    pub fn kind(&self) -> Kind {
308        let mut kind = Kind::never();
309
310        let n = self.kind;
311
312        if (n & kind::BYTES) == kind::BYTES {
313            kind.add_bytes();
314        }
315
316        if (n & kind::INTEGER) == kind::INTEGER {
317            kind.add_integer();
318        }
319
320        if (n & kind::FLOAT) == kind::FLOAT {
321            kind.add_float();
322        }
323
324        if (n & kind::BOOLEAN) == kind::BOOLEAN {
325            kind.add_boolean();
326        }
327
328        if (n & kind::OBJECT) == kind::OBJECT {
329            kind.add_object(Collection::any());
330        }
331
332        if (n & kind::ARRAY) == kind::ARRAY {
333            kind.add_array(Collection::any());
334        }
335
336        if (n & kind::TIMESTAMP) == kind::TIMESTAMP {
337            kind.add_timestamp();
338        }
339
340        if (n & kind::REGEX) == kind::REGEX {
341            kind.add_regex();
342        }
343
344        if (n & kind::NULL) == kind::NULL {
345            kind.add_null();
346        }
347
348        if (n & kind::UNDEFINED) == kind::UNDEFINED {
349            kind.add_undefined();
350        }
351
352        kind
353    }
354}
355
356// -----------------------------------------------------------------------------
357
358#[derive(Debug, Default, Clone)]
359pub struct ArgumentList {
360    pub(crate) arguments: HashMap<&'static str, Expr>,
361
362    /// A closure argument differs from regular arguments, in that it isn't an
363    /// expression by itself, and it also isn't tied to a parameter string in
364    /// the function call.
365    ///
366    /// We do still want to store the closure in the argument list, to allow
367    /// function implementors access to the closure through `Function::compile`.
368    closure: Option<Closure>,
369}
370
371impl ArgumentList {
372    #[must_use]
373    pub fn optional(&self, keyword: &'static str) -> Option<Box<dyn Expression>> {
374        self.optional_expr(keyword).map(|v| Box::new(v) as _)
375    }
376
377    #[must_use]
378    pub fn required(&self, keyword: &'static str) -> Box<dyn Expression> {
379        Box::new(self.required_expr(keyword)) as _
380    }
381
382    pub fn optional_literal(
383        &self,
384        keyword: &'static str,
385        state: &TypeState,
386    ) -> Result<Option<Value>, Error> {
387        self.optional_expr(keyword)
388            .map(|expr| match expr.resolve_constant(state) {
389                Some(value) => Ok(value),
390                _ => Err(Error::UnexpectedExpression {
391                    keyword,
392                    expected: "literal",
393                    expr,
394                }),
395            })
396            .transpose()
397    }
398
399    pub fn required_literal(
400        &self,
401        keyword: &'static str,
402        state: &TypeState,
403    ) -> Result<Value, Error> {
404        Ok(required(self.optional_literal(keyword, state)?))
405    }
406
407    pub fn optional_enum(
408        &self,
409        keyword: &'static str,
410        variants: &[Value],
411        state: &TypeState,
412    ) -> Result<Option<Value>, Error> {
413        self.optional_literal(keyword, state)?
414            .map(|value| {
415                variants
416                    .iter()
417                    .find(|v| *v == &value)
418                    .cloned()
419                    .ok_or(Error::InvalidEnumVariant {
420                        keyword,
421                        value,
422                        variants: variants.to_vec(),
423                    })
424            })
425            .transpose()
426    }
427
428    pub fn required_enum(
429        &self,
430        keyword: &'static str,
431        variants: &[Value],
432        state: &TypeState,
433    ) -> Result<Value, Error> {
434        Ok(required(self.optional_enum(keyword, variants, state)?))
435    }
436
437    pub fn optional_query(
438        &self,
439        keyword: &'static str,
440    ) -> Result<Option<crate::compiler::expression::Query>, Error> {
441        self.optional_expr(keyword)
442            .map(|expr| match expr {
443                Expr::Query(query) => Ok(query),
444                expr => Err(Error::UnexpectedExpression {
445                    keyword,
446                    expected: "query",
447                    expr,
448                }),
449            })
450            .transpose()
451    }
452
453    pub fn required_query(
454        &self,
455        keyword: &'static str,
456    ) -> Result<crate::compiler::expression::Query, Error> {
457        Ok(required(self.optional_query(keyword)?))
458    }
459
460    pub fn optional_regex(
461        &self,
462        keyword: &'static str,
463        state: &TypeState,
464    ) -> Result<Option<regex::Regex>, Error> {
465        self.optional_expr(keyword)
466            .map(|expr| match expr.resolve_constant(state) {
467                Some(Value::Regex(regex)) => Ok((*regex).clone()),
468                _ => Err(Error::UnexpectedExpression {
469                    keyword,
470                    expected: "regex",
471                    expr,
472                }),
473            })
474            .transpose()
475    }
476
477    pub fn required_regex(
478        &self,
479        keyword: &'static str,
480        state: &TypeState,
481    ) -> Result<regex::Regex, Error> {
482        Ok(required(self.optional_regex(keyword, state)?))
483    }
484
485    pub fn optional_object(
486        &self,
487        keyword: &'static str,
488    ) -> Result<Option<BTreeMap<KeyString, Expr>>, Error> {
489        self.optional_expr(keyword)
490            .map(|expr| match expr {
491                Expr::Container(Container {
492                    variant: Variant::Object(object),
493                }) => Ok((*object).clone()),
494                expr => Err(Error::UnexpectedExpression {
495                    keyword,
496                    expected: "object",
497                    expr,
498                }),
499            })
500            .transpose()
501    }
502
503    pub fn required_object(
504        &self,
505        keyword: &'static str,
506    ) -> Result<BTreeMap<KeyString, Expr>, Error> {
507        Ok(required(self.optional_object(keyword)?))
508    }
509
510    pub fn optional_array(&self, keyword: &'static str) -> Result<Option<Vec<Expr>>, Error> {
511        self.optional_expr(keyword)
512            .map(|expr| match expr {
513                Expr::Container(Container {
514                    variant: Variant::Array(array),
515                }) => Ok((*array).clone()),
516                expr => Err(Error::UnexpectedExpression {
517                    keyword,
518                    expected: "array",
519                    expr,
520                }),
521            })
522            .transpose()
523    }
524
525    pub fn required_array(&self, keyword: &'static str) -> Result<Vec<Expr>, Error> {
526        Ok(required(self.optional_array(keyword)?))
527    }
528
529    #[must_use]
530    pub fn optional_closure(&self) -> Option<&Closure> {
531        self.closure.as_ref()
532    }
533
534    pub fn required_closure(&self) -> Result<Closure, Error> {
535        self.optional_closure()
536            .cloned()
537            .ok_or(Error::ExpectedFunctionClosure)
538    }
539
540    pub(crate) fn keywords(&self) -> Vec<&'static str> {
541        self.arguments.keys().copied().collect::<Vec<_>>()
542    }
543
544    pub(crate) fn insert(&mut self, k: &'static str, v: Expr) {
545        self.arguments.insert(k, v);
546    }
547
548    pub(crate) fn set_closure(&mut self, closure: Closure) {
549        self.closure = Some(closure);
550    }
551
552    pub(crate) fn optional_expr(&self, keyword: &'static str) -> Option<Expr> {
553        self.arguments.get(keyword).cloned()
554    }
555
556    #[must_use]
557    pub fn required_expr(&self, keyword: &'static str) -> Expr {
558        required(self.optional_expr(keyword))
559    }
560}
561
562fn required<T>(argument: Option<T>) -> T {
563    argument.expect("invalid function signature")
564}
565
566#[cfg(any(test, feature = "test"))]
567mod test_impls {
568    use super::{ArgumentList, HashMap, Span, Value};
569    use crate::compiler::expression::FunctionArgument;
570    use crate::compiler::parser::Node;
571
572    impl From<HashMap<&'static str, Value>> for ArgumentList {
573        fn from(map: HashMap<&'static str, Value>) -> Self {
574            Self {
575                arguments: map
576                    .into_iter()
577                    .map(|(k, v)| (k, v.into()))
578                    .collect::<HashMap<_, _>>(),
579                closure: None,
580            }
581        }
582    }
583
584    impl From<ArgumentList> for Vec<(&'static str, Option<FunctionArgument>)> {
585        fn from(args: ArgumentList) -> Self {
586            args.arguments
587                .iter()
588                .map(|(key, expr)| {
589                    (
590                        *key,
591                        Some(FunctionArgument::new(
592                            None,
593                            Node::new(Span::default(), expr.clone()),
594                        )),
595                    )
596                })
597                .collect()
598        }
599    }
600}
601
602// -----------------------------------------------------------------------------
603
604#[derive(Debug, Clone, PartialEq)]
605pub struct Closure {
606    pub variables: Vec<Ident>,
607    pub block: Block,
608    pub block_type_def: TypeDef,
609}
610
611impl Closure {
612    #[must_use]
613    pub fn new<T: Into<Ident>>(variables: Vec<T>, block: Block, block_type_def: TypeDef) -> Self {
614        Self {
615            variables: variables.into_iter().map(Into::into).collect(),
616            block,
617            block_type_def,
618        }
619    }
620}
621
622// -----------------------------------------------------------------------------
623
624#[derive(thiserror::Error, Debug, PartialEq)]
625pub enum Error {
626    #[error("unexpected expression type")]
627    UnexpectedExpression {
628        keyword: &'static str,
629        expected: &'static str,
630        expr: Expr,
631    },
632
633    #[error(r#"invalid enum variant""#)]
634    InvalidEnumVariant {
635        keyword: &'static str,
636        value: Value,
637        variants: Vec<Value>,
638    },
639
640    #[error("this argument must be a static expression")]
641    ExpectedStaticExpression { keyword: &'static str, expr: Expr },
642
643    #[error("invalid argument")]
644    InvalidArgument {
645        keyword: &'static str,
646        value: Value,
647        error: &'static str,
648    },
649
650    #[error("missing function closure")]
651    ExpectedFunctionClosure,
652
653    #[error("mutation of read-only value")]
654    ReadOnlyMutation { context: String },
655}
656
657impl crate::diagnostic::DiagnosticMessage for Error {
658    fn code(&self) -> usize {
659        use Error::{
660            ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
661            ReadOnlyMutation, UnexpectedExpression,
662        };
663
664        match self {
665            UnexpectedExpression { .. } => 400,
666            InvalidEnumVariant { .. } => 401,
667            ExpectedStaticExpression { .. } => 402,
668            InvalidArgument { .. } => 403,
669            ExpectedFunctionClosure => 420,
670            ReadOnlyMutation { .. } => 315,
671        }
672    }
673
674    fn labels(&self) -> Vec<Label> {
675        use Error::{
676            ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
677            ReadOnlyMutation, UnexpectedExpression,
678        };
679
680        match self {
681            UnexpectedExpression {
682                keyword,
683                expected,
684                expr,
685            } => vec![
686                Label::primary(
687                    format!(r#"unexpected expression for argument "{keyword}""#),
688                    Span::default(),
689                ),
690                Label::context(format!("received: {}", expr.as_str()), Span::default()),
691                Label::context(format!("expected: {expected}"), Span::default()),
692            ],
693
694            InvalidEnumVariant {
695                keyword,
696                value,
697                variants,
698            } => vec![
699                Label::primary(
700                    format!(r#"invalid enum variant for argument "{keyword}""#),
701                    Span::default(),
702                ),
703                Label::context(format!("received: {value}"), Span::default()),
704                Label::context(
705                    format!(
706                        "expected one of: {}",
707                        variants
708                            .iter()
709                            .map(std::string::ToString::to_string)
710                            .collect::<Vec<_>>()
711                            .join(", ")
712                    ),
713                    Span::default(),
714                ),
715            ],
716
717            ExpectedStaticExpression { keyword, expr } => vec![
718                Label::primary(
719                    format!(r#"expected static expression for argument "{keyword}""#),
720                    Span::default(),
721                ),
722                Label::context(format!("received: {}", expr.as_str()), Span::default()),
723            ],
724
725            InvalidArgument {
726                keyword,
727                value,
728                error,
729            } => vec![
730                Label::primary(format!(r#"invalid argument "{keyword}""#), Span::default()),
731                Label::context(format!("received: {value}"), Span::default()),
732                Label::context(format!("error: {error}"), Span::default()),
733            ],
734
735            ExpectedFunctionClosure => vec![],
736            ReadOnlyMutation { context } => vec![
737                Label::primary("mutation of read-only value", Span::default()),
738                Label::context(context, Span::default()),
739            ],
740        }
741    }
742
743    fn notes(&self) -> Vec<Note> {
744        vec![Note::SeeCodeDocs(self.code())]
745    }
746}
747
748impl From<Error> for Box<dyn crate::diagnostic::DiagnosticMessage> {
749    fn from(error: Error) -> Self {
750        Box::new(error) as _
751    }
752}
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757
758    #[test]
759    fn test_parameter_kind() {
760        struct TestCase {
761            parameter_kind: u16,
762            kind: Kind,
763        }
764
765        for (
766            title,
767            TestCase {
768                parameter_kind,
769                kind,
770            },
771        ) in HashMap::from([
772            (
773                "bytes",
774                TestCase {
775                    parameter_kind: kind::BYTES,
776                    kind: Kind::bytes(),
777                },
778            ),
779            (
780                "integer",
781                TestCase {
782                    parameter_kind: kind::INTEGER,
783                    kind: Kind::integer(),
784                },
785            ),
786        ]) {
787            let parameter = Parameter::optional("", parameter_kind, "");
788
789            assert_eq!(parameter.kind(), kind, "{title}");
790        }
791    }
792}