vrl/stdlib/
del.rs

1use crate::compiler::prelude::*;
2use std::sync::LazyLock;
3
4static DEFAULT_COMPACT: LazyLock<Value> = LazyLock::new(|| Value::Boolean(false));
5
6static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
7    vec![
8        Parameter::required("target", kind::ANY, "The path of the field to delete"),
9        Parameter::optional(
10            "compact",
11            kind::BOOLEAN,
12            "After deletion, if `compact` is `true` and there is an empty object or array left,
13the empty object or array is also removed, cascading up to the root. This only
14applies to the path being deleted, and any parent paths.",
15        )
16        .default(&DEFAULT_COMPACT),
17    ]
18});
19
20#[inline]
21fn del(query: &expression::Query, compact: bool, ctx: &mut Context) -> Resolved {
22    let path = query.path();
23
24    if let Some(target_path) = query.external_path() {
25        Ok(ctx
26            .target_mut()
27            .target_remove(&target_path, compact)
28            .ok()
29            .flatten()
30            .unwrap_or(Value::Null))
31    } else if let Some(ident) = query.variable_ident() {
32        match ctx.state_mut().variable_mut(ident) {
33            Some(value) => {
34                let new_value = value.get(path).cloned();
35                value.remove(path, compact);
36                Ok(new_value.unwrap_or(Value::Null))
37            }
38            None => Ok(Value::Null),
39        }
40    } else if let Some(expr) = query.expression_target() {
41        let value = expr.resolve(ctx)?;
42
43        // No need to do the actual deletion, as the expression is only
44        // available as an argument to the function.
45        Ok(value.get(path).cloned().unwrap_or(Value::Null))
46    } else {
47        Ok(Value::Null)
48    }
49}
50
51#[derive(Clone, Copy, Debug)]
52pub struct Del;
53
54impl Function for Del {
55    fn identifier(&self) -> &'static str {
56        "del"
57    }
58
59    fn usage(&self) -> &'static str {
60        indoc! {"
61            Removes the field specified by the static `path` from the target.
62
63            For dynamic path deletion, see the `remove` function.
64        "}
65    }
66
67    fn category(&self) -> &'static str {
68        Category::Path.as_ref()
69    }
70
71    fn return_kind(&self) -> u16 {
72        kind::ANY
73    }
74
75    fn return_rules(&self) -> &'static [&'static str] {
76        &[
77            "Returns the value of the field being deleted. Returns `null` if the field doesn't exist.",
78        ]
79    }
80
81    fn notices(&self) -> &'static [&'static str] {
82        &[
83            "The `del` function _modifies the current event in place_ and returns the value of the deleted field.",
84        ]
85    }
86
87    fn pure(&self) -> bool {
88        false
89    }
90
91    fn parameters(&self) -> &'static [Parameter] {
92        PARAMETERS.as_slice()
93    }
94
95    fn examples(&self) -> &'static [Example] {
96        &[
97            example! {
98                title: "Delete a field",
99                source: indoc! {r#"
100                    . = { "foo": "bar" }
101                    del(.foo)
102                "#},
103                result: Ok("bar"),
104            },
105            example! {
106                title: "Rename a field",
107                source: indoc! {r#"
108                    . = { "old": "foo" }
109                    .new = del(.old)
110                    .
111                "#},
112                result: Ok(r#"{ "new": "foo" }"#),
113            },
114            example! {
115                title: "Returns null for unknown field",
116                source: r#"del({"foo": "bar"}.baz)"#,
117                result: Ok("null"),
118            },
119            example! {
120                title: "External target",
121                source: indoc! {r#"
122                    . = { "foo": true, "bar": 10 }
123                    del(.foo)
124                    .
125                "#},
126                result: Ok(r#"{ "bar": 10 }"#),
127            },
128            example! {
129                title: "Delete field from variable",
130                source: indoc! {r#"
131                    var = { "foo": true, "bar": 10 }
132                    del(var.foo)
133                    var
134                "#},
135                result: Ok(r#"{ "bar": 10 }"#),
136            },
137            example! {
138                title: "Delete object field",
139                source: indoc! {r#"
140                    var = { "foo": {"nested": true}, "bar": 10 }
141                    del(var.foo.nested, false)
142                    var
143                "#},
144                result: Ok(r#"{ "foo": {}, "bar": 10 }"#),
145            },
146            example! {
147                title: "Compact object field",
148                source: indoc! {r#"
149                    var = { "foo": {"nested": true}, "bar": 10 }
150                    del(var.foo.nested, true)
151                    var
152                "#},
153                result: Ok(r#"{ "bar": 10 }"#),
154            },
155        ]
156    }
157
158    fn compile(
159        &self,
160        _state: &state::TypeState,
161        ctx: &mut FunctionCompileContext,
162        arguments: ArgumentList,
163    ) -> Compiled {
164        let query = arguments.required_query("target")?;
165        let compact = arguments.optional("compact");
166
167        if let Some(target_path) = query.external_path()
168            && ctx.is_read_only_path(&target_path)
169        {
170            return Err(function::Error::ReadOnlyMutation {
171                context: format!("{query} is read-only, and cannot be deleted"),
172            }
173            .into());
174        }
175
176        Ok(Box::new(DelFn { query, compact }))
177    }
178}
179
180#[derive(Debug, Clone)]
181pub(crate) struct DelFn {
182    query: expression::Query,
183    compact: Option<Box<dyn Expression>>,
184}
185
186impl DelFn {
187    #[cfg(test)]
188    fn new(path: &str) -> Self {
189        use crate::path::{PathPrefix, parse_value_path};
190
191        Self {
192            query: expression::Query::new(
193                expression::Target::External(PathPrefix::Event),
194                parse_value_path(path).unwrap(),
195            ),
196            compact: None,
197        }
198    }
199}
200
201impl Expression for DelFn {
202    // TODO: we're silencing the result of the `remove` call here, to make this
203    // function infallible.
204    //
205    // This isn't correct though, since, while deleting Vector log fields is
206    // infallible, deleting metric fields is not.
207    //
208    // For example, if you try to delete `.name` in a metric event, the call
209    // returns an error, since this is an immutable field.
210    //
211    // After some debating, we've decided to _silently ignore_ deletions of
212    // immutable fields for now, but we'll circle back to this in the near
213    // future to potentially improve this situation.
214    //
215    // see tracking issue: https://github.com/vectordotdev/vector/issues/5887
216    fn resolve(&self, ctx: &mut Context) -> Resolved {
217        let compact = self
218            .compact
219            .map_resolve_with_default(ctx, || DEFAULT_COMPACT.clone())?
220            .try_boolean()?;
221        del(&self.query, compact, ctx)
222    }
223
224    fn type_info(&self, state: &state::TypeState) -> TypeInfo {
225        let mut state = state.clone();
226
227        let return_type = self.query.apply_type_info(&mut state).impure();
228
229        let compact: Option<bool> = self
230            .compact
231            .as_ref()
232            .and_then(|compact| compact.resolve_constant(&state))
233            .and_then(|compact| compact.as_boolean());
234
235        if let Some(compact) = compact {
236            self.query.delete_type_def(&mut state.external, compact);
237        } else {
238            let mut false_result = state.external.clone();
239            self.query.delete_type_def(&mut false_result, false);
240
241            let mut true_result = state.external.clone();
242            self.query.delete_type_def(&mut true_result, true);
243
244            state.external = false_result.merge(true_result);
245        }
246
247        TypeInfo::new(state, return_type)
248    }
249}
250
251impl fmt::Display for DelFn {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        f.write_str("")
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::btreemap;
261    use crate::value;
262
263    #[test]
264    fn del() {
265        let cases = vec![
266            (
267                // String field exists
268                btreemap! { "exists" => "value" },
269                Ok(value!("value")),
270                DelFn::new("exists"),
271            ),
272            (
273                // String field doesn't exist
274                btreemap! { "exists" => "value" },
275                Ok(value!(null)),
276                DelFn::new("does_not_exist"),
277            ),
278            (
279                // Array field exists
280                btreemap! { "exists" => value!([1, 2, 3]) },
281                Ok(value!([1, 2, 3])),
282                DelFn::new("exists"),
283            ),
284            (
285                // Null field exists
286                btreemap! { "exists" => value!(null) },
287                Ok(value!(null)),
288                DelFn::new("exists"),
289            ),
290            (
291                // Map field exists
292                btreemap! {"exists" => btreemap! { "foo" => "bar" }},
293                Ok(value!(btreemap! {"foo" => "bar" })),
294                DelFn::new("exists"),
295            ),
296            (
297                // Integer field exists
298                btreemap! { "exists" => 127 },
299                Ok(value!(127)),
300                DelFn::new("exists"),
301            ),
302            (
303                // Array field exists
304                btreemap! {"exists" => value!([1, 2, 3]) },
305                Ok(value!(2)),
306                DelFn::new(".exists[1]"),
307            ),
308        ];
309        let tz = TimeZone::default();
310        for (object, exp, func) in cases {
311            let mut object: Value = object.into();
312            let mut runtime_state = state::RuntimeState::default();
313            let mut ctx = Context::new(&mut object, &mut runtime_state, &tz);
314            let got = func
315                .resolve(&mut ctx)
316                .map_err(|e| format!("{:#}", anyhow::anyhow!(e)));
317            assert_eq!(got, exp);
318        }
319    }
320}