jsonptr/delete.rs
1//! # Delete values based on JSON Pointers
2//!
3//! This module provides the [`Delete`] trait which is implemented by types that
4//! can internally remove a value based on a JSON Pointer.
5//!
6//! The rules of deletion are determined by the implementation, with the
7//! provided implementations (`"json"` & `"toml"`) operating as follows:
8//! - If the [`Pointer`] can be resolved, then the [`Value`](`Delete::Value`) is
9//! deleted and returned as `Some(value)`.
10//! - If the [`Pointer`] fails to resolve for any reason, `Ok(None)` is
11//! returned.
12//! - If the [`Pointer`] is root, `value` is replaced:
13//! - `"json"` - `serde_json::Value::Null`
14//! - `"toml"` - `toml::Value::Table::Default`
15//!
16//! This module is enabled by default with the `"delete"` feature flag.
17//!
18//! ## Usage
19//! Deleting a resolved pointer:
20//! ```rust
21//! use jsonptr::{Pointer, delete::Delete};
22//! use serde_json::json;
23//!
24//! let mut data = json!({ "foo": { "bar": { "baz": "qux" } } });
25//! let ptr = Pointer::from_static("/foo/bar/baz");
26//! assert_eq!(data.delete(&ptr), Some("qux".into()));
27//! assert_eq!(data, json!({ "foo": { "bar": {} } }));
28//! ```
29//! Deleting a non-existent Pointer returns `None`:
30//! ```rust
31//! use jsonptr::{ Pointer, delete::Delete };
32//! use serde_json::json;
33//!
34//! let mut data = json!({});
35//! let ptr = Pointer::from_static("/foo/bar/baz");
36//! assert_eq!(ptr.delete(&mut data), None);
37//! assert_eq!(data, json!({}));
38//! ```
39//! Deleting a root pointer replaces the value with `Value::Null`:
40//! ```rust
41//! use jsonptr::{Pointer, delete::Delete};
42//! use serde_json::json;
43//!
44//! let mut data = json!({ "foo": { "bar": "baz" } });
45//! let ptr = Pointer::root();
46//! assert_eq!(data.delete(&ptr), Some(json!({ "foo": { "bar": "baz" } })));
47//! assert!(data.is_null());
48//! ```
49//!
50//! ## Provided implementations
51//!
52//! | Lang | value type | feature flag | Default |
53//! | ----- |: ----------------- :|: ---------- :| ------- |
54//! | JSON | `serde_json::Value` | `"json"` | ✓ |
55//! | TOML | `toml::Value` | `"toml"` | |
56
57use crate::Pointer;
58
59/*
60░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
61╔══════════════════════════════════════════════════════════════════════════════╗
62║ ║
63║ Delete ║
64║ ¯¯¯¯¯¯¯¯ ║
65╚══════════════════════════════════════════════════════════════════════════════╝
66░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
67*/
68
69/// Delete is implemented by types which can internally remove a value based on
70/// a JSON Pointer
71pub trait Delete {
72 /// The type of value that this implementation can operate on.
73 type Value;
74
75 /// Attempts to internally delete a value based upon a [Pointer].
76 fn delete(&mut self, ptr: &Pointer) -> Option<Self::Value>;
77}
78
79/*
80░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
81╔══════════════════════════════════════════════════════════════════════════════╗
82║ ║
83║ json impl ║
84║ ¯¯¯¯¯¯¯¯¯¯¯ ║
85╚══════════════════════════════════════════════════════════════════════════════╝
86░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
87*/
88
89#[cfg(feature = "json")]
90mod json {
91 use super::Delete;
92 use crate::Pointer;
93 use core::mem;
94 use serde_json::Value;
95
96 impl Delete for Value {
97 type Value = Value;
98 fn delete(&mut self, ptr: &Pointer) -> Option<Self::Value> {
99 let Some((parent_ptr, last)) = ptr.split_back() else {
100 // deleting at root
101 return Some(mem::replace(self, Value::Null));
102 };
103 parent_ptr
104 .resolve_mut(self)
105 .ok()
106 .and_then(|parent| match parent {
107 Value::Array(children) => {
108 let idx = last.to_index().ok()?.for_len_incl(children.len()).ok()?;
109 children.remove(idx).into()
110 }
111 Value::Object(children) => children.remove(last.decoded().as_ref()),
112 _ => None,
113 })
114 }
115 }
116}
117
118/*
119░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
120╔══════════════════════════════════════════════════════════════════════════════╗
121║ ║
122║ toml impl ║
123║ ¯¯¯¯¯¯¯¯¯¯¯ ║
124╚══════════════════════════════════════════════════════════════════════════════╝
125░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
126*/
127#[cfg(feature = "toml")]
128mod toml {
129 use super::Delete;
130 use crate::Pointer;
131 use core::mem;
132 use toml::{Table, Value};
133
134 impl Delete for Value {
135 type Value = Value;
136 fn delete(&mut self, ptr: &Pointer) -> Option<Self::Value> {
137 let Some((parent_ptr, last)) = ptr.split_back() else {
138 // deleting at root
139 return Some(mem::replace(self, Table::default().into()));
140 };
141 parent_ptr
142 .resolve_mut(self)
143 .ok()
144 .and_then(|parent| match parent {
145 Value::Array(children) => {
146 let idx = last.to_index().ok()?.for_len_incl(children.len()).ok()?;
147 children.remove(idx).into()
148 }
149 Value::Table(children) => children.remove(last.decoded().as_ref()),
150 _ => None,
151 })
152 }
153 }
154}
155
156/*
157░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
158╔══════════════════════════════════════════════════════════════════════════════╗
159║ ║
160║ Tests ║
161║ ¯¯¯¯¯¯¯ ║
162╚══════════════════════════════════════════════════════════════════════════════╝
163░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
164*/
165
166#[cfg(test)]
167mod tests {
168 use super::Delete;
169 use crate::Pointer;
170 use core::fmt;
171
172 use serde_json::json;
173 struct Test<V> {
174 data: V,
175 ptr: &'static str,
176 expected_data: V,
177 expected_deleted: Option<V>,
178 }
179 impl<V> Test<V>
180 where
181 V: Delete<Value = V> + Clone + PartialEq + fmt::Display + fmt::Debug,
182 {
183 fn all(tests: impl IntoIterator<Item = Test<V>>) {
184 tests.into_iter().enumerate().for_each(|(i, t)| t.run(i));
185 }
186 fn run(self, _i: usize) {
187 let Test {
188 mut data,
189 ptr,
190 expected_data,
191 expected_deleted,
192 } = self;
193
194 let ptr = Pointer::from_static(ptr);
195 let deleted = ptr.delete(&mut data);
196 assert_eq!(expected_data, data);
197 assert_eq!(expected_deleted, deleted);
198 }
199 }
200 /*
201 ╔═══════════════════════════════════════════════════╗
202 ║ json ║
203 ╚═══════════════════════════════════════════════════╝
204 */
205 #[test]
206 #[cfg(feature = "json")]
207 fn delete_json() {
208 Test::all([
209 // 0
210 Test {
211 ptr: "/foo",
212 data: json!({"foo": "bar"}),
213 expected_data: json!({}),
214 expected_deleted: Some(json!("bar")),
215 },
216 // 1
217 Test {
218 ptr: "/foo/bar",
219 data: json!({"foo": {"bar": "baz"}}),
220 expected_data: json!({"foo": {}}),
221 expected_deleted: Some(json!("baz")),
222 },
223 // 2
224 Test {
225 ptr: "/foo/bar",
226 data: json!({"foo": "bar"}),
227 expected_data: json!({"foo": "bar"}),
228 expected_deleted: None,
229 },
230 // 3
231 Test {
232 ptr: "/foo/bar",
233 data: json!({"foo": {"bar": "baz"}}),
234 expected_data: json!({"foo": {}}),
235 expected_deleted: Some(json!("baz")),
236 },
237 // 4
238 Test {
239 ptr: "/foo/bar/0",
240 data: json!({"foo": {"bar": ["baz", "qux"]}}),
241 expected_data: json!({"foo": {"bar": ["qux"]}}),
242 expected_deleted: Some(json!("baz")),
243 },
244 // 5
245 Test {
246 ptr: "/foo/0",
247 data: json!({"foo": "bar"}),
248 expected_data: json!({"foo": "bar"}),
249 expected_deleted: None,
250 },
251 // 6
252 Test {
253 ptr: "/foo/bar/0/baz",
254 data: json!({"foo": { "bar": [{"baz": "qux", "remaining": "field"}]}}),
255 expected_data: json!({"foo": { "bar": [{"remaining": "field"}]} }),
256 expected_deleted: Some(json!("qux")),
257 },
258 // 7
259 // issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18
260 Test {
261 ptr: "/Example",
262 data: json!({"Example": 21, "test": "test"}),
263 expected_data: json!({"test": "test"}),
264 expected_deleted: Some(json!(21)),
265 },
266 Test {
267 ptr: "",
268 data: json!({"Example": 21, "test": "test"}),
269 expected_data: json!(null),
270 expected_deleted: Some(json!({"Example": 21, "test": "test"})),
271 },
272 ]);
273 }
274 /*
275 ╔═══════════════════════════════════════════════════╗
276 ║ toml ║
277 ╚═══════════════════════════════════════════════════╝
278 */
279 #[test]
280 #[cfg(feature = "toml")]
281 fn delete_toml() {
282 use toml::{toml, Table, Value};
283
284 Test::all([
285 // 0
286 Test {
287 data: toml! {"foo" = "bar"}.into(),
288 ptr: "/foo",
289 expected_data: Value::Table(Table::new()),
290 expected_deleted: Some("bar".into()),
291 },
292 // 1
293 Test {
294 data: toml! {"foo" = {"bar" = "baz"}}.into(),
295 ptr: "/foo/bar",
296 expected_data: toml! {"foo" = {}}.into(),
297 expected_deleted: Some("baz".into()),
298 },
299 // 2
300 Test {
301 data: toml! {"foo" = "bar"}.into(),
302 ptr: "/foo/bar",
303 expected_data: toml! {"foo" = "bar"}.into(),
304 expected_deleted: None,
305 },
306 // 3
307 Test {
308 data: toml! {"foo" = {"bar" = "baz"}}.into(),
309 ptr: "/foo/bar",
310 expected_data: toml! {"foo" = {}}.into(),
311 expected_deleted: Some("baz".into()),
312 },
313 // 4
314 Test {
315 data: toml! {"foo" = {"bar" = ["baz", "qux"]}}.into(),
316 ptr: "/foo/bar/0",
317 expected_data: toml! {"foo" = {"bar" = ["qux"]}}.into(),
318 expected_deleted: Some("baz".into()),
319 },
320 // 5
321 Test {
322 data: toml! {"foo" = "bar"}.into(),
323 ptr: "/foo/0",
324 expected_data: toml! {"foo" = "bar"}.into(),
325 expected_deleted: None,
326 },
327 // 6
328 Test {
329 data: toml! {"foo" = { "bar" = [{"baz" = "qux", "remaining" = "field"}]}}.into(),
330 ptr: "/foo/bar/0/baz",
331 expected_data: toml! {"foo" = { "bar" = [{"remaining" = "field"}]} }.into(),
332 expected_deleted: Some("qux".into()),
333 },
334 // 7
335 // issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18
336 Test {
337 data: toml! {"Example" = 21 "test" = "test"}.into(),
338 ptr: "/Example",
339 expected_data: toml! {"test" = "test"}.into(),
340 expected_deleted: Some(21.into()),
341 },
342 ]);
343 }
344}