vrl/stdlib/
is_json.rs

1use crate::compiler::function::EnumVariant;
2use crate::compiler::prelude::*;
3use crate::value;
4
5static VARIANT_ENUM: &[EnumVariant] = &[
6    EnumVariant {
7        value: "object",
8        description: "JSON object - {}",
9    },
10    EnumVariant {
11        value: "array",
12        description: "JSON array - []",
13    },
14    EnumVariant {
15        value: "string",
16        description: "JSON-formatted string values wrapped with quote marks",
17    },
18    EnumVariant {
19        value: "number",
20        description: "Integer or float numbers",
21    },
22    EnumVariant {
23        value: "bool",
24        description: "True or false",
25    },
26    EnumVariant {
27        value: "null",
28        description: "Exact null value",
29    },
30];
31
32static PARAMETERS: &[Parameter] = &[
33    Parameter::required(
34        "value",
35        kind::BYTES,
36        "The value to check if it is a valid JSON document.",
37    ),
38    Parameter::optional(
39        "variant",
40        kind::BYTES,
41        "The variant of the JSON type to explicitly check for.",
42    )
43    .enum_variants(VARIANT_ENUM),
44];
45
46fn is_json(value: Value) -> Resolved {
47    let bytes = value.try_bytes()?;
48
49    match serde_json::from_slice::<'_, serde::de::IgnoredAny>(&bytes) {
50        Ok(_) => Ok(value!(true)),
51        Err(_) => Ok(value!(false)),
52    }
53}
54
55fn is_json_with_variant(value: Value, variant: &Bytes) -> Resolved {
56    let bytes = value.try_bytes()?;
57
58    if serde_json::from_slice::<'_, serde::de::IgnoredAny>(&bytes).is_ok() {
59        for c in bytes {
60            return match c {
61                // Search for the first non whitespace char
62                b' ' | b'\n' | b'\t' | b'\r' => continue,
63                b'{' => Ok(value!(variant.as_ref() == b"object")),
64                b'[' => Ok(value!(variant.as_ref() == b"array")),
65                b't' | b'f' => Ok(value!(variant.as_ref() == b"bool")),
66                b'-' | b'0'..=b'9' => Ok(value!(variant.as_ref() == b"number")),
67                b'"' => Ok(value!(variant.as_ref() == b"string")),
68                b'n' => Ok(value!(variant.as_ref() == b"null")),
69                _ => break,
70            };
71        }
72    }
73
74    Ok(value!(false))
75}
76
77fn variants() -> Vec<Value> {
78    vec![
79        value!("object"),
80        value!("array"),
81        value!("bool"),
82        value!("number"),
83        value!("string"),
84        value!("null"),
85    ]
86}
87
88#[derive(Clone, Copy, Debug)]
89pub struct IsJson;
90
91impl Function for IsJson {
92    fn identifier(&self) -> &'static str {
93        "is_json"
94    }
95
96    fn usage(&self) -> &'static str {
97        "Check if the string is a valid JSON document."
98    }
99
100    fn category(&self) -> &'static str {
101        Category::Type.as_ref()
102    }
103
104    fn return_kind(&self) -> u16 {
105        kind::BOOLEAN
106    }
107
108    fn return_rules(&self) -> &'static [&'static str] {
109        &[
110            "Returns `true` if `value` is a valid JSON document.",
111            "Returns `false` if `value` is not JSON-formatted.",
112        ]
113    }
114
115    fn parameters(&self) -> &'static [Parameter] {
116        PARAMETERS
117    }
118
119    fn examples(&self) -> &'static [Example] {
120        &[
121            example! {
122                title: "Valid JSON object",
123                source: r#"is_json("{}")"#,
124                result: Ok("true"),
125            },
126            example! {
127                title: "Non-valid value",
128                source: r#"is_json("{")"#,
129                result: Ok("false"),
130            },
131            example! {
132                title: "Exact variant",
133                source: r#"is_json("{}", variant: "object")"#,
134                result: Ok("true"),
135            },
136            example! {
137                title: "Non-valid exact variant",
138                source: r#"is_json("{}", variant: "array")"#,
139                result: Ok("false"),
140            },
141            example! {
142                title: "Valid JSON string",
143                source: r#"is_json(s'"test"')"#,
144                result: Ok("true"),
145            },
146        ]
147    }
148
149    fn compile(
150        &self,
151        state: &state::TypeState,
152        _ctx: &mut FunctionCompileContext,
153        arguments: ArgumentList,
154    ) -> Compiled {
155        let value = arguments.required("value");
156        let variant = arguments.optional_enum("variant", &variants(), state)?;
157
158        match variant {
159            Some(raw_variant) => {
160                let variant = raw_variant
161                    .try_bytes()
162                    .map_err(|e| Box::new(e) as Box<dyn DiagnosticMessage>)?;
163                Ok(IsJsonVariantsFn { value, variant }.as_expr())
164            }
165            None => Ok(IsJsonFn { value }.as_expr()),
166        }
167    }
168}
169
170#[derive(Clone, Debug)]
171struct IsJsonFn {
172    value: Box<dyn Expression>,
173}
174
175impl FunctionExpression for IsJsonFn {
176    fn resolve(&self, ctx: &mut Context) -> Resolved {
177        let value = self.value.resolve(ctx)?;
178        is_json(value)
179    }
180
181    fn type_def(&self, _: &state::TypeState) -> TypeDef {
182        TypeDef::boolean().infallible()
183    }
184}
185
186#[derive(Clone, Debug)]
187struct IsJsonVariantsFn {
188    value: Box<dyn Expression>,
189    variant: Bytes,
190}
191
192impl FunctionExpression for IsJsonVariantsFn {
193    fn resolve(&self, ctx: &mut Context) -> Resolved {
194        let value = self.value.resolve(ctx)?;
195        let variant = &self.variant;
196
197        is_json_with_variant(value, variant)
198    }
199
200    fn type_def(&self, _: &state::TypeState) -> TypeDef {
201        TypeDef::boolean().infallible()
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    test_function![
210        is_json => IsJson;
211
212        object {
213            args: func_args![value: "{}"],
214            want: Ok(value!(true)),
215            tdef: TypeDef::boolean().infallible(),
216        }
217
218        string {
219            args: func_args![value: r#""test""#],
220            want: Ok(value!(true)),
221            tdef: TypeDef::boolean().infallible(),
222        }
223
224        invalid {
225            args: func_args![value: "}{"],
226            want: Ok(value!(false)),
227            tdef: TypeDef::boolean().infallible(),
228        }
229
230        exact_variant {
231            args: func_args![value: "{}", variant: "object"],
232            want: Ok(value!(true)),
233            tdef: TypeDef::boolean().infallible(),
234        }
235
236        exact_variant_invalid {
237            args: func_args![value: "123", variant: "null"],
238            want: Ok(value!(false)),
239            tdef: TypeDef::boolean().infallible(),
240        }
241
242        variant_with_spaces {
243            args: func_args![value: "   []", variant: "array"],
244            want: Ok(value!(true)),
245            tdef: TypeDef::boolean().infallible(),
246        }
247
248        invalid_variant {
249            args: func_args![value: "[]", variant: "invalid-variant"],
250            want: Err(r#"invalid enum variant""#),
251            tdef: TypeDef::boolean().infallible(),
252        }
253
254        invalid_variant_type {
255            args: func_args![value: "[]", variant: 100],
256            want: Err(r#"invalid enum variant""#),
257            tdef: TypeDef::boolean().infallible(),
258        }
259    ];
260}