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 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 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 btreemap! { "exists" => "value" },
269 Ok(value!("value")),
270 DelFn::new("exists"),
271 ),
272 (
273 btreemap! { "exists" => "value" },
275 Ok(value!(null)),
276 DelFn::new("does_not_exist"),
277 ),
278 (
279 btreemap! { "exists" => value!([1, 2, 3]) },
281 Ok(value!([1, 2, 3])),
282 DelFn::new("exists"),
283 ),
284 (
285 btreemap! { "exists" => value!(null) },
287 Ok(value!(null)),
288 DelFn::new("exists"),
289 ),
290 (
291 btreemap! {"exists" => btreemap! { "foo" => "bar" }},
293 Ok(value!(btreemap! {"foo" => "bar" })),
294 DelFn::new("exists"),
295 ),
296 (
297 btreemap! { "exists" => 127 },
299 Ok(value!(127)),
300 DelFn::new("exists"),
301 ),
302 (
303 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}