vrl/stdlib/
parse_yaml.rs

1use crate::compiler::prelude::*;
2use crate::stdlib::json_utils::bom::StripBomFromUTF8;
3use crate::stdlib::json_utils::json_type_def::json_type_def;
4
5fn parse_yaml(value: Value) -> Resolved {
6    Ok(serde_yaml_ng::from_slice(value.try_bytes()?.strip_bom())
7        .map_err(|e| format!("unable to parse yaml: {e}"))?)
8}
9
10#[derive(Clone, Copy, Debug)]
11pub struct ParseYaml;
12
13impl Function for ParseYaml {
14    fn identifier(&self) -> &'static str {
15        "parse_yaml"
16    }
17
18    fn summary(&self) -> &'static str {
19        "parse a string to a YAML type"
20    }
21
22    fn usage(&self) -> &'static str {
23        indoc! {"
24            Parses the provided `value` as YAML.
25
26            Only YAML types are returned. If you need to convert a `string` into a `timestamp`,
27            consider the `parse_timestamp` function.
28        "}
29    }
30
31    fn category(&self) -> &'static str {
32        Category::Parse.as_ref()
33    }
34
35    fn internal_failure_reasons(&self) -> &'static [&'static str] {
36        &["`value` is not a valid YAML-formatted payload."]
37    }
38
39    fn return_kind(&self) -> u16 {
40        kind::BOOLEAN
41            | kind::INTEGER
42            | kind::FLOAT
43            | kind::BYTES
44            | kind::OBJECT
45            | kind::ARRAY
46            | kind::NULL
47    }
48
49    fn notices(&self) -> &'static [&'static str] {
50        &[indoc! {"
51            Only YAML types are returned. If you need to convert a `string` into a `timestamp`,
52            consider the [`parse_timestamp`](#parse_timestamp) function.
53        "}]
54    }
55
56    fn parameters(&self) -> &'static [Parameter] {
57        const PARAMETERS: &[Parameter] = &[Parameter::required(
58            "value",
59            kind::BYTES,
60            "The string representation of the YAML to parse.",
61        )];
62
63        PARAMETERS
64    }
65
66    fn examples(&self) -> &'static [Example] {
67        &[
68            example! {
69                title: "Parse simple YAML",
70                source: r"parse_yaml!(s'key: val')",
71                result: Ok(r#"{ "key": "val" }"#),
72            },
73            example! {
74                title: "Parse YAML",
75                source: r#"parse_yaml!(s'
76                    object:
77                        string: value
78                        number: 42
79                        boolean: false
80                        array:
81                        - hello
82                        - world
83                        json_array: ["hello", "world"]
84                ')"#,
85                result: Ok(r#"{
86                    "object": {
87                        "string": "value",
88                        "number": 42,
89                        "boolean": false,
90                        "array": [
91                            "hello",
92                            "world"
93                        ],
94                        "json_array": [
95                            "hello",
96                            "world"
97                        ]
98                    }
99                }"#),
100            },
101            example! {
102                title: "Parse YAML string",
103                source: r"parse_yaml!(s'hello')",
104                result: Ok("hello"),
105            },
106            example! {
107                title: "Parse YAML quoted string",
108                source: r#"parse_yaml!(s'"hello"')"#,
109                result: Ok("hello"),
110            },
111            example! {
112                title: "Parse YAML integer",
113                source: r#"parse_yaml!("42")"#,
114                result: Ok("42"),
115            },
116            example! {
117                title: "Parse YAML float",
118                source: r#"parse_yaml!("42.13")"#,
119                result: Ok("42.13"),
120            },
121            example! {
122                title: "Parse YAML boolean",
123                source: r#"parse_yaml!("false")"#,
124                result: Ok("false"),
125            },
126            example! {
127                title: "Parse embedded JSON",
128                source: r#"parse_yaml!(s'{"key": "val"}')"#,
129                result: Ok(r#"{ "key": "val" }"#),
130            },
131            example! {
132                title: "Parse embedded JSON array",
133                source: r#"parse_yaml!("[true, 0]")"#,
134                result: Ok("[true, 0]"),
135            },
136        ]
137    }
138
139    fn compile(
140        &self,
141        _state: &state::TypeState,
142        _ctx: &mut FunctionCompileContext,
143        arguments: ArgumentList,
144    ) -> Compiled {
145        let value = arguments.required("value");
146
147        Ok(ParseYamlFn { value }.as_expr())
148    }
149}
150
151#[derive(Debug, Clone)]
152struct ParseYamlFn {
153    value: Box<dyn Expression>,
154}
155
156impl FunctionExpression for ParseYamlFn {
157    fn resolve(&self, ctx: &mut Context) -> Resolved {
158        let value = self.value.resolve(ctx)?;
159        parse_yaml(value)
160    }
161
162    fn type_def(&self, _: &state::TypeState) -> TypeDef {
163        json_type_def()
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use crate::value;
171
172    test_function![
173        parse_yaml => ParseYaml;
174
175        parses {
176            args: func_args![ value: r"
177                field: value
178            " ],
179            want: Ok(value!({ field: "value" })),
180            tdef: json_type_def(),
181        }
182
183        complex_yaml {
184            args: func_args![ value: r#"
185                object:
186                    string: value
187                    number: 42
188                    boolean: false
189                    array:
190                    - hello
191                    - world
192                    json_array: ["hello", "world"]
193            "# ],
194            want: Ok(value!({ object: {string: "value", number: 42, boolean: false, array: ["hello", "world"], json_array: ["hello", "world"]} })),
195            tdef: json_type_def(),
196        }
197
198        parses_json {
199            args: func_args![ value: r#"{"field": "value"}"# ],
200            want: Ok(value!({ field: "value" })),
201            tdef: json_type_def(),
202        }
203
204        complex_json {
205            args: func_args![ value: r#"{"object": {"string":"value","number":42,"array":["hello","world"],"boolean":false}}"# ],
206            want: Ok(value!({ object: {string: "value", number: 42, array: ["hello", "world"], boolean: false} })),
207            tdef: json_type_def(),
208        }
209
210        incomplete_json_errors {
211            args: func_args![ value: r#"{"field": "value"# ],
212            want: Err(
213                r"unable to parse yaml: found unexpected end of stream at line 1 column 17, while scanning a quoted scalar at line 1 column 11"
214            ),
215            tdef: json_type_def(),
216        }
217    ];
218}