1use crate::compiler::prelude::*;
2use crate::path::{OwnedSegment, OwnedValuePath};
3
4fn remove(path: Value, compact: Value, mut value: Value) -> Resolved {
5 let path = match path {
6 Value::Array(path) => {
7 let mut lookup = OwnedValuePath::root();
8
9 for segment in path {
10 let segment = match segment {
11 Value::Bytes(field) => {
12 OwnedSegment::Field(String::from_utf8_lossy(&field).into())
13 }
14 #[allow(clippy::cast_possible_truncation)] Value::Integer(index) => OwnedSegment::Index(index as isize),
16 value => {
17 return Err(format!(
18 "path segment must be either string or integer, not {}",
19 value.kind()
20 )
21 .into());
22 }
23 };
24
25 lookup.segments.push(segment);
26 }
27
28 lookup
29 }
30 value => {
31 return Err(ValueError::Expected {
32 got: value.kind(),
33 expected: Kind::array(Collection::any()),
34 }
35 .into());
36 }
37 };
38 let compact = compact.try_boolean()?;
39 value.remove(&path, compact);
40 Ok(value)
41}
42
43#[derive(Clone, Copy, Debug)]
44pub struct Remove;
45
46impl Function for Remove {
47 fn identifier(&self) -> &'static str {
48 "remove"
49 }
50
51 fn parameters(&self) -> &'static [Parameter] {
52 &[
53 Parameter {
54 keyword: "value",
55 kind: kind::OBJECT | kind::ARRAY,
56 required: true,
57 },
58 Parameter {
59 keyword: "path",
60 kind: kind::ARRAY,
61 required: true,
62 },
63 Parameter {
64 keyword: "compact",
65 kind: kind::BOOLEAN,
66 required: false,
67 },
68 ]
69 }
70
71 fn examples(&self) -> &'static [Example] {
72 &[
73 Example {
74 title: "remove existing field",
75 source: r#"remove!(value: {"foo": "bar"}, path: ["foo"])"#,
76 result: Ok("{}"),
77 },
78 Example {
79 title: "remove unknown field",
80 source: r#"remove!(value: {"foo": "bar"}, path: ["baz"])"#,
81 result: Ok(r#"{ "foo": "bar" }"#),
82 },
83 Example {
84 title: "nested path",
85 source: r#"remove!(value: {"foo": { "bar": true }}, path: ["foo", "bar"])"#,
86 result: Ok(r#"{ "foo": {} }"#),
87 },
88 Example {
89 title: "compact object",
90 source: r#"remove!(value: {"foo": { "bar": true }}, path: ["foo", "bar"], compact: true)"#,
91 result: Ok("{}"),
92 },
93 Example {
94 title: "indexing",
95 source: "remove!(value: [92, 42], path: [0])",
96 result: Ok("[42]"),
97 },
98 Example {
99 title: "nested indexing",
100 source: r#"remove!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1])"#,
101 result: Ok(r#"{ "foo": { "bar": [92] } }"#),
102 },
103 Example {
104 title: "compact array",
105 source: r#"remove!(value: {"foo": [42], "bar": true }, path: ["foo", 0], compact: true)"#,
106 result: Ok(r#"{ "bar": true }"#),
107 },
108 Example {
109 title: "external target",
110 source: indoc! {r#"
111 . = { "foo": true }
112 remove!(value: ., path: ["foo"])
113 "#},
114 result: Ok("{}"),
115 },
116 Example {
117 title: "variable",
118 source: indoc! {r#"
119 var = { "foo": true }
120 remove!(value: var, path: ["foo"])
121 "#},
122 result: Ok("{}"),
123 },
124 Example {
125 title: "missing index",
126 source: r#"remove!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1, -1])"#,
127 result: Ok(r#"{ "foo": { "bar": [92, 42] } }"#),
128 },
129 Example {
130 title: "invalid indexing",
131 source: r#"remove!(value: [42], path: ["foo"])"#,
132 result: Ok("[42]"),
133 },
134 Example {
135 title: "invalid segment type",
136 source: r#"remove!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", true])"#,
137 result: Err(
138 r#"function call error for "remove" at (0:65): path segment must be either string or integer, not boolean"#,
139 ),
140 },
141 ]
142 }
143
144 fn compile(
145 &self,
146 _state: &state::TypeState,
147 _ctx: &mut FunctionCompileContext,
148 arguments: ArgumentList,
149 ) -> Compiled {
150 let value = arguments.required("value");
151 let path = arguments.required("path");
152 let compact = arguments.optional("compact").unwrap_or(expr!(false));
153
154 Ok(RemoveFn {
155 value,
156 path,
157 compact,
158 }
159 .as_expr())
160 }
161}
162
163#[derive(Debug, Clone)]
164pub(crate) struct RemoveFn {
165 value: Box<dyn Expression>,
166 path: Box<dyn Expression>,
167 compact: Box<dyn Expression>,
168}
169
170impl FunctionExpression for RemoveFn {
171 fn resolve(&self, ctx: &mut Context) -> Resolved {
172 let path = self.path.resolve(ctx)?;
173 let compact = self.compact.resolve(ctx)?;
174 let value = self.value.resolve(ctx)?;
175
176 remove(path, compact, value)
177 }
178
179 fn type_def(&self, state: &state::TypeState) -> TypeDef {
180 let value_td = self.value.type_def(state);
181
182 let mut td = TypeDef::from(Kind::never()).fallible();
183
184 if value_td.is_array() {
185 td = td.or_array(Collection::any());
186 }
187
188 if value_td.is_object() {
189 td = td.or_object(Collection::any());
190 }
191
192 td
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::value;
200
201 test_function![
202 remove => Remove;
203
204 array {
205 args: func_args![value: value!([42]), path: value!([0])],
206 want: Ok(value!([])),
207 tdef: TypeDef::array(Collection::any()).fallible(),
208 }
209
210 object {
211 args: func_args![value: value!({ "foo": 42 }), path: value!(["foo"])],
212 want: Ok(value!({})),
213 tdef: TypeDef::object(Collection::any()).fallible(),
214 }
215 ];
216}