vrl/stdlib/
is_nullish.rs

1use super::util;
2use crate::compiler::prelude::*;
3
4fn is_nullish(value: &Value) -> bool {
5    util::is_nullish(value)
6}
7
8#[derive(Clone, Copy, Debug)]
9pub struct IsNullish;
10
11impl Function for IsNullish {
12    fn identifier(&self) -> &'static str {
13        "is_nullish"
14    }
15
16    fn usage(&self) -> &'static str {
17        r#"Determines whether `value` is nullish. Returns `true` if the specified `value` is `null`, an empty string, a string containing only whitespace, or the string `"-"`. Returns `false` otherwise."#
18    }
19
20    fn category(&self) -> &'static str {
21        Category::Type.as_ref()
22    }
23
24    fn return_kind(&self) -> u16 {
25        kind::BOOLEAN
26    }
27
28    fn return_rules(&self) -> &'static [&'static str] {
29        &[
30            "Returns `true` if `value` is `null`.",
31            "Returns `true` if `value` is `\"-\"`.",
32            "Returns `true` if `value` is whitespace as defined by [Unicode `White_Space` property](https://en.wikipedia.org/wiki/Unicode_character_property#Whitespace).",
33            "Returns `false` if `value` is anything else.",
34        ]
35    }
36
37    fn notices(&self) -> &'static [&'static str] {
38        &[indoc! {r#"
39            This function behaves inconsistently: it returns `false` for empty arrays (`[]`) and
40            objects (`{}`), but `true` for empty strings (`""`) and `null`.
41        "#}]
42    }
43
44    fn parameters(&self) -> &'static [Parameter] {
45        const PARAMETERS: &[Parameter] = &[Parameter::required(
46            "value",
47            kind::ANY,
48            "The value to check for nullishness, for example, a useless value.",
49        )];
50        PARAMETERS
51    }
52
53    fn examples(&self) -> &'static [Example] {
54        &[
55            example! {
56                title: "Null detection (blank string)",
57                source: r#"is_nullish("")"#,
58                result: Ok("true"),
59            },
60            example! {
61                title: "Null detection (dash string)",
62                source: r#"is_nullish("-")"#,
63                result: Ok("true"),
64            },
65            example! {
66                title: "Null detection (whitespace)",
67                source: "is_nullish(\"\n  \n\")",
68                result: Ok("true"),
69            },
70            example! {
71                title: "Null",
72                source: "is_nullish(null)",
73                result: Ok("true"),
74            },
75        ]
76    }
77
78    fn compile(
79        &self,
80        _state: &state::TypeState,
81        _ctx: &mut FunctionCompileContext,
82        arguments: ArgumentList,
83    ) -> Compiled {
84        let value = arguments.required("value");
85        Ok(IsNullishFn { value }.as_expr())
86    }
87}
88
89#[derive(Clone, Debug)]
90struct IsNullishFn {
91    value: Box<dyn Expression>,
92}
93
94impl FunctionExpression for IsNullishFn {
95    fn resolve(&self, ctx: &mut Context) -> Resolved {
96        let value = self.value.resolve(ctx)?;
97        Ok(is_nullish(&value).into())
98    }
99
100    fn type_def(&self, _: &state::TypeState) -> TypeDef {
101        TypeDef::boolean().infallible()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::value;
109    test_function![
110        is_nullish => IsNullish;
111
112        empty_string {
113            args: func_args![value: value!("")],
114            want: Ok(value!(true)),
115            tdef: TypeDef::boolean().infallible(),
116        }
117
118        single_space_string {
119            args: func_args![value: value!(" ")],
120            want: Ok(value!(true)),
121            tdef: TypeDef::boolean().infallible(),
122        }
123
124        multi_space_string {
125            args: func_args![value: value!("     ")],
126            want: Ok(value!(true)),
127            tdef: TypeDef::boolean().infallible(),
128        }
129
130        newline_string {
131            args: func_args![value: value!("\n")],
132            want: Ok(value!(true)),
133            tdef: TypeDef::boolean().infallible(),
134        }
135
136        carriage_return_string {
137            args: func_args![value: value!("\r")],
138            want: Ok(value!(true)),
139            tdef: TypeDef::boolean().infallible(),
140        }
141
142        dash_string {
143            args: func_args![value: value!("-")],
144            want: Ok(value!(true)),
145            tdef: TypeDef::boolean().infallible(),
146        }
147
148        null {
149            args: func_args![value: value!(null)],
150            want: Ok(value!(true)),
151            tdef: TypeDef::boolean().infallible(),
152        }
153
154        non_empty_string {
155            args: func_args![value: value!("hello world")],
156            want: Ok(value!(false)),
157            tdef: TypeDef::boolean().infallible(),
158        }
159
160        // Shows that a non-string/null literal returns false
161        integer {
162            args: func_args![value: value!(427)],
163            want: Ok(value!(false)),
164            tdef: TypeDef::boolean().infallible(),
165        }
166
167        // Shows that a non-literal type returns false
168        array {
169            args: func_args![value: value!([1, 2, 3])],
170            want: Ok(value!(false)),
171            tdef: TypeDef::boolean().infallible(),
172        }
173    ];
174}