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}