vrl/stdlib/
get.rs

1use crate::compiler::prelude::*;
2use crate::path::{OwnedSegment, OwnedValuePath};
3
4#[allow(clippy::cast_possible_truncation)] // TODO consider removal options
5fn get(value: &Value, value_path: Value) -> Resolved {
6    let path = match value_path {
7        Value::Array(array) => {
8            let mut path = OwnedValuePath::root();
9
10            for segment in array {
11                let segment = match segment {
12                    Value::Bytes(field) => {
13                        OwnedSegment::field(String::from_utf8_lossy(&field).as_ref())
14                    }
15                    Value::Integer(index) => OwnedSegment::index(index as isize),
16                    value => {
17                        return Err(format!(
18                            "path segment must be either string or integer, not {}",
19                            value.kind()
20                        )
21                        .into());
22                    }
23                };
24                path.push(segment);
25            }
26
27            path
28        }
29        value => {
30            return Err(ValueError::Expected {
31                got: value.kind(),
32                expected: Kind::array(Collection::any()),
33            }
34            .into());
35        }
36    };
37    Ok(value.get(&path).cloned().unwrap_or(Value::Null))
38}
39
40#[derive(Clone, Copy, Debug)]
41pub struct Get;
42
43impl Function for Get {
44    fn identifier(&self) -> &'static str {
45        "get"
46    }
47
48    fn usage(&self) -> &'static str {
49        indoc! {"
50            Dynamically get the value of a given path.
51
52            If you know the path you want to look up, use
53            static paths such as `.foo.bar[1]` to get the value of that
54            path. However, if you do not know the path names,
55            use the dynamic `get` function to get the requested value.
56        "}
57    }
58
59    fn category(&self) -> &'static str {
60        Category::Path.as_ref()
61    }
62
63    fn internal_failure_reasons(&self) -> &'static [&'static str] {
64        &["The `path` segment must be a string or an integer."]
65    }
66
67    fn return_kind(&self) -> u16 {
68        kind::ANY
69    }
70
71    fn parameters(&self) -> &'static [Parameter] {
72        const PARAMETERS: &[Parameter] = &[
73            Parameter::required(
74                "value",
75                kind::OBJECT | kind::ARRAY,
76                "The object or array to query.",
77            ),
78            Parameter::required(
79                "path",
80                kind::ARRAY,
81                "An array of path segments to look for the value.",
82            ),
83        ];
84        PARAMETERS
85    }
86
87    fn examples(&self) -> &'static [Example] {
88        &[
89            example! {
90                title: "Single-segment top-level field",
91                source: r#"get!(value: {"foo": "bar"}, path: ["foo"])"#,
92                result: Ok(r#""bar""#),
93            },
94            example! {
95                title: "Returns null for unknown field",
96                source: r#"get!(value: {"foo": "bar"}, path: ["baz"])"#,
97                result: Ok("null"),
98            },
99            example! {
100                title: "Multi-segment nested field",
101                source: r#"get!(value: {"foo": { "bar": true }}, path: ["foo", "bar"])"#,
102                result: Ok("true"),
103            },
104            example! {
105                title: "Array indexing",
106                source: "get!(value: [92, 42], path: [0])",
107                result: Ok("92"),
108            },
109            example! {
110                title: "Array indexing (negative)",
111                source: r#"get!(value: ["foo", "bar", "baz"], path: [-2])"#,
112                result: Ok(r#""bar""#),
113            },
114            example! {
115                title: "Nested indexing",
116                source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1])"#,
117                result: Ok("42"),
118            },
119            example! {
120                title: "External target",
121                source: r#"get!(value: ., path: ["foo"])"#,
122                input: r#"{ "foo": true }"#,
123                result: Ok("true"),
124            },
125            example! {
126                title: "Variable",
127                source: indoc! {r#"
128                    var = { "foo": true }
129                    get!(value: var, path: ["foo"])
130                "#},
131                result: Ok("true"),
132            },
133            example! {
134                title: "Missing index",
135                source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1, -1])"#,
136                result: Ok("null"),
137            },
138            example! {
139                title: "Invalid indexing",
140                source: r#"get!(value: [42], path: ["foo"])"#,
141                result: Ok("null"),
142            },
143            example! {
144                title: "Invalid segment type",
145                source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", true])"#,
146                result: Err(
147                    r#"function call error for "get" at (0:62): path segment must be either string or integer, not boolean"#,
148                ),
149            },
150        ]
151    }
152
153    fn compile(
154        &self,
155        _state: &state::TypeState,
156        _ctx: &mut FunctionCompileContext,
157        arguments: ArgumentList,
158    ) -> Compiled {
159        let value = arguments.required("value");
160        let path = arguments.required("path");
161
162        Ok(GetFn { value, path }.as_expr())
163    }
164}
165
166#[derive(Debug, Clone)]
167pub(crate) struct GetFn {
168    value: Box<dyn Expression>,
169    path: Box<dyn Expression>,
170}
171
172impl FunctionExpression for GetFn {
173    fn resolve(&self, ctx: &mut Context) -> Resolved {
174        let path = self.path.resolve(ctx)?;
175        let value = self.value.resolve(ctx)?;
176
177        get(&value, path)
178    }
179
180    fn type_def(&self, _: &state::TypeState) -> TypeDef {
181        TypeDef::any().fallible()
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::value;
189
190    test_function![
191        get => Get;
192
193        any {
194            args: func_args![value: value!([42]), path: value!([0])],
195            want: Ok(42),
196            tdef: TypeDef::any().fallible(),
197        }
198    ];
199}