vrl/stdlib/
map_values.rs

1use crate::compiler::prelude::*;
2use std::sync::LazyLock;
3
4static DEFAULT_RECURSIVE: LazyLock<Value> = LazyLock::new(|| Value::Boolean(false));
5
6static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
7    vec![
8        Parameter::required(
9            "value",
10            kind::OBJECT | kind::ARRAY,
11            "The object or array to iterate.",
12        ),
13        Parameter::optional(
14            "recursive",
15            kind::BOOLEAN,
16            "Whether to recursively iterate the collection.",
17        )
18        .default(&DEFAULT_RECURSIVE),
19    ]
20});
21
22fn map_values<T>(
23    value: Value,
24    recursive: bool,
25    ctx: &mut Context,
26    runner: &closure::Runner<T>,
27) -> Resolved
28where
29    T: Fn(&mut Context) -> Resolved,
30{
31    let mut iter = value.into_iter(recursive);
32
33    for item in iter.by_ref() {
34        let value = match item {
35            IterItem::KeyValue(_, value)
36            | IterItem::IndexValue(_, value)
37            | IterItem::Value(value) => value,
38        };
39
40        runner.map_value(ctx, value)?;
41    }
42
43    Ok(iter.into())
44}
45
46#[derive(Clone, Copy, Debug)]
47pub struct MapValues;
48
49impl Function for MapValues {
50    fn identifier(&self) -> &'static str {
51        "map_values"
52    }
53
54    fn usage(&self) -> &'static str {
55        indoc! {"
56            Map the values within a collection.
57
58            If `recursive` is enabled, the function iterates into nested
59            collections, using the following rules:
60
61            1. Iteration starts at the root.
62            2. For every nested collection type:
63               - First return the collection type itself.
64               - Then recurse into the collection, and loop back to item (1)
65                 in the list
66               - Any mutation done on a collection *before* recursing into it,
67                 are preserved.
68
69            The function uses the function closure syntax to allow mutating
70            the value for each item in the collection.
71
72            The same scoping rules apply to closure blocks as they do for
73            regular blocks, meaning, any variable defined in parent scopes
74            are accessible, and mutations to those variables are preserved,
75            but any new variables instantiated in the closure block are
76            unavailable outside of the block.
77
78            Check out the examples below to learn about the closure syntax.
79        "}
80    }
81
82    fn category(&self) -> &'static str {
83        Category::Enumerate.as_ref()
84    }
85
86    fn return_kind(&self) -> u16 {
87        kind::ARRAY | kind::OBJECT
88    }
89
90    fn parameters(&self) -> &'static [Parameter] {
91        PARAMETERS.as_slice()
92    }
93
94    fn examples(&self) -> &'static [Example] {
95        &[
96            example! {
97                title: "Upcase values",
98                source: indoc! {r#"
99                    . = {
100                        "foo": "foo",
101                        "bar": "bar"
102                    }
103                    map_values(.) -> |value| { upcase(value) }
104                "#},
105                result: Ok(r#"{ "foo": "FOO", "bar": "BAR" }"#),
106            },
107            example! {
108                title: "Recursively map object values",
109                source: indoc! {r#"
110                    val = {
111                        "a": 1,
112                        "b": [{ "c": 2 }, { "d": 3 }],
113                        "e": { "f": 4 }
114                    }
115                    map_values(val, recursive: true) -> |value| {
116                        if is_integer(value) { int!(value) + 1 } else { value }
117                    }
118                "#},
119                result: Ok(r#"{ "a": 2, "b": [{ "c": 3 }, { "d": 4 }], "e": { "f": 5 } }"#),
120            },
121        ]
122    }
123
124    fn compile(
125        &self,
126        _state: &state::TypeState,
127        _ctx: &mut FunctionCompileContext,
128        arguments: ArgumentList,
129    ) -> Compiled {
130        let value = arguments.required("value");
131        let recursive = arguments.optional("recursive");
132        let closure = arguments.required_closure()?;
133
134        Ok(MapValuesFn {
135            value,
136            recursive,
137            closure,
138        }
139        .as_expr())
140    }
141
142    fn closure(&self) -> Option<closure::Definition> {
143        use closure::{Definition, Input, Output, Variable, VariableKind};
144
145        Some(Definition {
146            inputs: vec![Input {
147                parameter_keyword: "value",
148                kind: Kind::object(Collection::any()).or_array(Collection::any()),
149                variables: vec![Variable {
150                    kind: VariableKind::TargetInnerValue,
151                }],
152                output: Output::Kind(Kind::any()),
153                example: example! {
154                    title: "map object values",
155                    source: r#"map_values({ "one" : "one", "two": "two" }) -> |value| { upcase(value) }"#,
156                    result: Ok(r#"{ "one": "ONE", "two": "TWO" }"#),
157                },
158            }],
159            is_iterator: true,
160        })
161    }
162}
163
164#[derive(Debug, Clone)]
165struct MapValuesFn {
166    value: Box<dyn Expression>,
167    recursive: Option<Box<dyn Expression>>,
168    closure: Closure,
169}
170
171impl FunctionExpression for MapValuesFn {
172    fn resolve(&self, ctx: &mut Context) -> ExpressionResult<Value> {
173        let recursive = self
174            .recursive
175            .map_resolve_with_default(ctx, || DEFAULT_RECURSIVE.clone())?
176            .try_boolean()?;
177
178        let value = self.value.resolve(ctx)?;
179        let Closure {
180            variables,
181            block,
182            block_type_def: _,
183        } = &self.closure;
184        let runner = closure::Runner::new(variables, |ctx| block.resolve(ctx));
185
186        map_values(value, recursive, ctx, &runner)
187    }
188
189    fn type_def(&self, ctx: &state::TypeState) -> TypeDef {
190        let mut value = self.value.type_def(ctx);
191        let closure = self.closure.block_type_def.kind().clone();
192
193        recursive_type_def(&mut value, closure, true);
194        value
195    }
196}
197
198fn recursive_type_def(from: &mut Kind, to: Kind, root: bool) {
199    if let Some(object) = from.as_object_mut() {
200        for v in object.known_mut().values_mut() {
201            recursive_type_def(v, to.clone(), false);
202        }
203    }
204
205    if let Some(array) = from.as_array_mut() {
206        for v in array.known_mut().values_mut() {
207            recursive_type_def(v, to.clone(), false);
208        }
209    }
210
211    if !root {
212        *from = to;
213    }
214}