> */ = PointerBuf::parse(ptr_str).unwrap_err();
166 | assert!(err.original().is_no_leading_slash());
167 | ```
168 |
169 | ## Feature Flags
170 |
171 | | Flag | Description | Enables | Default |
172 | | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------- | :-----: |
173 | | `"std"` | Implements `std::error::Error` for error types | | ✓ |
174 | | `"serde"` | Enables [`serde`] support for types | | ✓ |
175 | | `"json"` | Implements ops for [`serde_json::Value`] | `"serde"` | ✓ |
176 | | `"toml"` | Implements ops for [`toml::Value`] | `"std"`, `toml` | |
177 | | `"assign"` | Enables the [`assign`] module and related pointer methods, providing a means to assign a value to a specific location within a document | | ✓ |
178 | | `"resolve"` | Enables the [`resolve`] module and related pointer methods, providing a means to resolve a value at a specific location within a document | | ✓ |
179 | | `"delete"` | Enables the [`delete`] module and related pointer methods, providing a means to delete a value at a specific location within a document | `"resolve"` | ✓ |
180 | | `"miette"` | Enables integration with [`miette`](https://docs.rs/miette) for error reporting | `"std"` | |
181 |
182 |
183 |
184 | ## License
185 |
186 | Licensed under either of
187 |
188 | - Apache License, Version 2.0
189 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
190 | - MIT license
191 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
192 |
193 | at your convenience.
194 |
195 | ## Contribution
196 |
197 | Contributions and feedback are always welcome and appreciated. If you find an
198 | issue, please open a ticket or a pull request.
199 |
200 | Unless you explicitly state otherwise, any contribution intentionally submitted
201 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
202 | dual licensed as above, without any additional terms or conditions.
203 |
204 | [LICENSE-APACHE]: LICENSE-APACHE
205 | [LICENSE-MIT]: LICENSE-MIT
206 |
207 |
208 |
209 | [`Pointer::components`]: https://docs.rs/jsonptr/latest/jsonptrstruct.Pointer.html#method.components
210 | [`Pointer::tokens`]: https://docs.rs/jsonptr/latest/jsonptrstruct.Pointer.html#method.tokens
211 | [`Pointer`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html
212 | [`Pointer::parse`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.parse
213 | [`Pointer::resolve`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.resolve
214 | [`Pointer::resolve_mut`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.resolve_mut
215 | [`Pointer::assign`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.assign
216 | [`Pointer::delete`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.delete
217 | [`PointerBuf::parse`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html#method.parse
218 | [`from_tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html#method.from_tokens
219 | [`PointerBuf`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html
220 | [`Token`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Token.html
221 | [`Tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Tokens.html
222 | [`Components`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Components.html
223 | [`Component`]: https://docs.rs/jsonptr/latest/jsonptr/enum.Component.html
224 | [`index`]: https://docs.rs/jsonptr/latest/jsonptr/index/index.html
225 | [`tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.tokens
226 | [`components`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.components
227 | [`resolve`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/index.html
228 | [`assign`]: https://docs.rs/jsonptr/latest/jsonptr/assign/index.html
229 | [`delete`]: https://docs.rs/jsonptr/latest/jsonptr/delete/index.html
230 | [`Resolve`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/trait.Resolve.html
231 | [`ResolveMut`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/trait.ResolveMut.html
232 | [`Assign`]: https://docs.rs/jsonptr/latest/jsonptr/assign/trait.Assign.html
233 | [`Delete`]: https://docs.rs/jsonptr/latest/jsonptr/delete/trait.Delete.html
234 | [`serde`]: https://docs.rs/serde/1.0/serde/index
235 | [`serde_json`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html
236 | [`serde_json::Value`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html
237 | [`toml`]: https://docs.rs/toml/0.8/toml/enum.Value.html
238 | [`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html
239 | [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
240 | [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html
241 |
--------------------------------------------------------------------------------
/src/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, `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 |
57 | use 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
71 | pub 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;
77 | }
78 |
79 | /*
80 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
81 | ╔══════════════════════════════════════════════════════════════════════════════╗
82 | ║ ║
83 | ║ json impl ║
84 | ║ ¯¯¯¯¯¯¯¯¯¯¯ ║
85 | ╚══════════════════════════════════════════════════════════════════════════════╝
86 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
87 | */
88 |
89 | #[cfg(feature = "json")]
90 | mod 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 {
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")]
128 | mod 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 {
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)]
167 | mod tests {
168 | use super::Delete;
169 | use crate::Pointer;
170 | use core::fmt;
171 |
172 | use serde_json::json;
173 | struct Test {
174 | data: V,
175 | ptr: &'static str,
176 | expected_data: V,
177 | expected_deleted: Option,
178 | }
179 | impl Test
180 | where
181 | V: Delete + Clone + PartialEq + fmt::Display + fmt::Debug,
182 | {
183 | fn all(tests: impl IntoIterator- >) {
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 | }
345 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.24.2"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler2"
16 | version = "2.0.0"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19 |
20 | [[package]]
21 | name = "aho-corasick"
22 | version = "1.1.3"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
25 | dependencies = [
26 | "memchr",
27 | ]
28 |
29 | [[package]]
30 | name = "backtrace"
31 | version = "0.3.74"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
34 | dependencies = [
35 | "addr2line",
36 | "cfg-if",
37 | "libc",
38 | "miniz_oxide",
39 | "object",
40 | "rustc-demangle",
41 | "windows-targets",
42 | ]
43 |
44 | [[package]]
45 | name = "backtrace-ext"
46 | version = "0.2.1"
47 | source = "registry+https://github.com/rust-lang/crates.io-index"
48 | checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
49 | dependencies = [
50 | "backtrace",
51 | ]
52 |
53 | [[package]]
54 | name = "bitflags"
55 | version = "2.6.0"
56 | source = "registry+https://github.com/rust-lang/crates.io-index"
57 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
58 |
59 | [[package]]
60 | name = "cfg-if"
61 | version = "1.0.0"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
64 |
65 | [[package]]
66 | name = "env_logger"
67 | version = "0.8.4"
68 | source = "registry+https://github.com/rust-lang/crates.io-index"
69 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
70 | dependencies = [
71 | "log",
72 | "regex",
73 | ]
74 |
75 | [[package]]
76 | name = "equivalent"
77 | version = "1.0.1"
78 | source = "registry+https://github.com/rust-lang/crates.io-index"
79 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
80 |
81 | [[package]]
82 | name = "errno"
83 | version = "0.3.9"
84 | source = "registry+https://github.com/rust-lang/crates.io-index"
85 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
86 | dependencies = [
87 | "libc",
88 | "windows-sys 0.52.0",
89 | ]
90 |
91 | [[package]]
92 | name = "getrandom"
93 | version = "0.2.15"
94 | source = "registry+https://github.com/rust-lang/crates.io-index"
95 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
96 | dependencies = [
97 | "cfg-if",
98 | "libc",
99 | "wasi",
100 | ]
101 |
102 | [[package]]
103 | name = "gimli"
104 | version = "0.31.1"
105 | source = "registry+https://github.com/rust-lang/crates.io-index"
106 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
107 |
108 | [[package]]
109 | name = "hashbrown"
110 | version = "0.15.1"
111 | source = "registry+https://github.com/rust-lang/crates.io-index"
112 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
113 |
114 | [[package]]
115 | name = "indexmap"
116 | version = "2.6.0"
117 | source = "registry+https://github.com/rust-lang/crates.io-index"
118 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
119 | dependencies = [
120 | "equivalent",
121 | "hashbrown",
122 | ]
123 |
124 | [[package]]
125 | name = "is_ci"
126 | version = "1.2.0"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
129 |
130 | [[package]]
131 | name = "itoa"
132 | version = "1.0.11"
133 | source = "registry+https://github.com/rust-lang/crates.io-index"
134 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
135 |
136 | [[package]]
137 | name = "jsonptr"
138 | version = "0.7.1"
139 | dependencies = [
140 | "miette",
141 | "quickcheck",
142 | "quickcheck_macros",
143 | "serde",
144 | "serde_json",
145 | "syn 1.0.109",
146 | "toml",
147 | ]
148 |
149 | [[package]]
150 | name = "libc"
151 | version = "0.2.162"
152 | source = "registry+https://github.com/rust-lang/crates.io-index"
153 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
154 |
155 | [[package]]
156 | name = "linux-raw-sys"
157 | version = "0.4.14"
158 | source = "registry+https://github.com/rust-lang/crates.io-index"
159 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
160 |
161 | [[package]]
162 | name = "log"
163 | version = "0.4.22"
164 | source = "registry+https://github.com/rust-lang/crates.io-index"
165 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
166 |
167 | [[package]]
168 | name = "memchr"
169 | version = "2.7.4"
170 | source = "registry+https://github.com/rust-lang/crates.io-index"
171 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
172 |
173 | [[package]]
174 | name = "miette"
175 | version = "7.4.0"
176 | source = "registry+https://github.com/rust-lang/crates.io-index"
177 | checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64"
178 | dependencies = [
179 | "backtrace",
180 | "backtrace-ext",
181 | "cfg-if",
182 | "miette-derive",
183 | "owo-colors",
184 | "supports-color",
185 | "supports-hyperlinks",
186 | "supports-unicode",
187 | "terminal_size",
188 | "textwrap",
189 | "thiserror",
190 | "unicode-width",
191 | ]
192 |
193 | [[package]]
194 | name = "miette-derive"
195 | version = "7.4.0"
196 | source = "registry+https://github.com/rust-lang/crates.io-index"
197 | checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67"
198 | dependencies = [
199 | "proc-macro2",
200 | "quote",
201 | "syn 2.0.87",
202 | ]
203 |
204 | [[package]]
205 | name = "miniz_oxide"
206 | version = "0.8.0"
207 | source = "registry+https://github.com/rust-lang/crates.io-index"
208 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
209 | dependencies = [
210 | "adler2",
211 | ]
212 |
213 | [[package]]
214 | name = "object"
215 | version = "0.36.5"
216 | source = "registry+https://github.com/rust-lang/crates.io-index"
217 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
218 | dependencies = [
219 | "memchr",
220 | ]
221 |
222 | [[package]]
223 | name = "owo-colors"
224 | version = "4.1.0"
225 | source = "registry+https://github.com/rust-lang/crates.io-index"
226 | checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
227 |
228 | [[package]]
229 | name = "proc-macro2"
230 | version = "1.0.89"
231 | source = "registry+https://github.com/rust-lang/crates.io-index"
232 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
233 | dependencies = [
234 | "unicode-ident",
235 | ]
236 |
237 | [[package]]
238 | name = "quickcheck"
239 | version = "1.0.3"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
242 | dependencies = [
243 | "env_logger",
244 | "log",
245 | "rand",
246 | ]
247 |
248 | [[package]]
249 | name = "quickcheck_macros"
250 | version = "1.0.0"
251 | source = "registry+https://github.com/rust-lang/crates.io-index"
252 | checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
253 | dependencies = [
254 | "proc-macro2",
255 | "quote",
256 | "syn 1.0.109",
257 | ]
258 |
259 | [[package]]
260 | name = "quote"
261 | version = "1.0.37"
262 | source = "registry+https://github.com/rust-lang/crates.io-index"
263 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
264 | dependencies = [
265 | "proc-macro2",
266 | ]
267 |
268 | [[package]]
269 | name = "rand"
270 | version = "0.8.5"
271 | source = "registry+https://github.com/rust-lang/crates.io-index"
272 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
273 | dependencies = [
274 | "rand_core",
275 | ]
276 |
277 | [[package]]
278 | name = "rand_core"
279 | version = "0.6.4"
280 | source = "registry+https://github.com/rust-lang/crates.io-index"
281 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
282 | dependencies = [
283 | "getrandom",
284 | ]
285 |
286 | [[package]]
287 | name = "regex"
288 | version = "1.11.1"
289 | source = "registry+https://github.com/rust-lang/crates.io-index"
290 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
291 | dependencies = [
292 | "aho-corasick",
293 | "memchr",
294 | "regex-automata",
295 | "regex-syntax",
296 | ]
297 |
298 | [[package]]
299 | name = "regex-automata"
300 | version = "0.4.9"
301 | source = "registry+https://github.com/rust-lang/crates.io-index"
302 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
303 | dependencies = [
304 | "aho-corasick",
305 | "memchr",
306 | "regex-syntax",
307 | ]
308 |
309 | [[package]]
310 | name = "regex-syntax"
311 | version = "0.8.5"
312 | source = "registry+https://github.com/rust-lang/crates.io-index"
313 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
314 |
315 | [[package]]
316 | name = "rustc-demangle"
317 | version = "0.1.24"
318 | source = "registry+https://github.com/rust-lang/crates.io-index"
319 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
320 |
321 | [[package]]
322 | name = "rustix"
323 | version = "0.38.40"
324 | source = "registry+https://github.com/rust-lang/crates.io-index"
325 | checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
326 | dependencies = [
327 | "bitflags",
328 | "errno",
329 | "libc",
330 | "linux-raw-sys",
331 | "windows-sys 0.52.0",
332 | ]
333 |
334 | [[package]]
335 | name = "ryu"
336 | version = "1.0.18"
337 | source = "registry+https://github.com/rust-lang/crates.io-index"
338 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
339 |
340 | [[package]]
341 | name = "serde"
342 | version = "1.0.228"
343 | source = "registry+https://github.com/rust-lang/crates.io-index"
344 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
345 | dependencies = [
346 | "serde_core",
347 | ]
348 |
349 | [[package]]
350 | name = "serde_core"
351 | version = "1.0.228"
352 | source = "registry+https://github.com/rust-lang/crates.io-index"
353 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
354 | dependencies = [
355 | "serde_derive",
356 | ]
357 |
358 | [[package]]
359 | name = "serde_derive"
360 | version = "1.0.228"
361 | source = "registry+https://github.com/rust-lang/crates.io-index"
362 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
363 | dependencies = [
364 | "proc-macro2",
365 | "quote",
366 | "syn 2.0.87",
367 | ]
368 |
369 | [[package]]
370 | name = "serde_json"
371 | version = "1.0.140"
372 | source = "registry+https://github.com/rust-lang/crates.io-index"
373 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
374 | dependencies = [
375 | "itoa",
376 | "memchr",
377 | "ryu",
378 | "serde",
379 | ]
380 |
381 | [[package]]
382 | name = "serde_spanned"
383 | version = "1.0.3"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
386 | dependencies = [
387 | "serde_core",
388 | ]
389 |
390 | [[package]]
391 | name = "supports-color"
392 | version = "3.0.1"
393 | source = "registry+https://github.com/rust-lang/crates.io-index"
394 | checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77"
395 | dependencies = [
396 | "is_ci",
397 | ]
398 |
399 | [[package]]
400 | name = "supports-hyperlinks"
401 | version = "3.0.0"
402 | source = "registry+https://github.com/rust-lang/crates.io-index"
403 | checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee"
404 |
405 | [[package]]
406 | name = "supports-unicode"
407 | version = "3.0.0"
408 | source = "registry+https://github.com/rust-lang/crates.io-index"
409 | checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
410 |
411 | [[package]]
412 | name = "syn"
413 | version = "1.0.109"
414 | source = "registry+https://github.com/rust-lang/crates.io-index"
415 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
416 | dependencies = [
417 | "proc-macro2",
418 | "quote",
419 | "unicode-ident",
420 | ]
421 |
422 | [[package]]
423 | name = "syn"
424 | version = "2.0.87"
425 | source = "registry+https://github.com/rust-lang/crates.io-index"
426 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
427 | dependencies = [
428 | "proc-macro2",
429 | "quote",
430 | "unicode-ident",
431 | ]
432 |
433 | [[package]]
434 | name = "terminal_size"
435 | version = "0.4.1"
436 | source = "registry+https://github.com/rust-lang/crates.io-index"
437 | checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
438 | dependencies = [
439 | "rustix",
440 | "windows-sys 0.59.0",
441 | ]
442 |
443 | [[package]]
444 | name = "textwrap"
445 | version = "0.16.1"
446 | source = "registry+https://github.com/rust-lang/crates.io-index"
447 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
448 | dependencies = [
449 | "unicode-linebreak",
450 | "unicode-width",
451 | ]
452 |
453 | [[package]]
454 | name = "thiserror"
455 | version = "1.0.69"
456 | source = "registry+https://github.com/rust-lang/crates.io-index"
457 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
458 | dependencies = [
459 | "thiserror-impl",
460 | ]
461 |
462 | [[package]]
463 | name = "thiserror-impl"
464 | version = "1.0.69"
465 | source = "registry+https://github.com/rust-lang/crates.io-index"
466 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
467 | dependencies = [
468 | "proc-macro2",
469 | "quote",
470 | "syn 2.0.87",
471 | ]
472 |
473 | [[package]]
474 | name = "toml"
475 | version = "0.9.5"
476 | source = "registry+https://github.com/rust-lang/crates.io-index"
477 | checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
478 | dependencies = [
479 | "indexmap",
480 | "serde",
481 | "serde_spanned",
482 | "toml_datetime",
483 | "toml_parser",
484 | "toml_writer",
485 | "winnow",
486 | ]
487 |
488 | [[package]]
489 | name = "toml_datetime"
490 | version = "0.7.3"
491 | source = "registry+https://github.com/rust-lang/crates.io-index"
492 | checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
493 | dependencies = [
494 | "serde_core",
495 | ]
496 |
497 | [[package]]
498 | name = "toml_parser"
499 | version = "1.0.4"
500 | source = "registry+https://github.com/rust-lang/crates.io-index"
501 | checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
502 | dependencies = [
503 | "winnow",
504 | ]
505 |
506 | [[package]]
507 | name = "toml_writer"
508 | version = "1.0.4"
509 | source = "registry+https://github.com/rust-lang/crates.io-index"
510 | checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
511 |
512 | [[package]]
513 | name = "unicode-ident"
514 | version = "1.0.13"
515 | source = "registry+https://github.com/rust-lang/crates.io-index"
516 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
517 |
518 | [[package]]
519 | name = "unicode-linebreak"
520 | version = "0.1.5"
521 | source = "registry+https://github.com/rust-lang/crates.io-index"
522 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
523 |
524 | [[package]]
525 | name = "unicode-width"
526 | version = "0.1.14"
527 | source = "registry+https://github.com/rust-lang/crates.io-index"
528 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
529 |
530 | [[package]]
531 | name = "wasi"
532 | version = "0.11.0+wasi-snapshot-preview1"
533 | source = "registry+https://github.com/rust-lang/crates.io-index"
534 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
535 |
536 | [[package]]
537 | name = "windows-sys"
538 | version = "0.52.0"
539 | source = "registry+https://github.com/rust-lang/crates.io-index"
540 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
541 | dependencies = [
542 | "windows-targets",
543 | ]
544 |
545 | [[package]]
546 | name = "windows-sys"
547 | version = "0.59.0"
548 | source = "registry+https://github.com/rust-lang/crates.io-index"
549 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
550 | dependencies = [
551 | "windows-targets",
552 | ]
553 |
554 | [[package]]
555 | name = "windows-targets"
556 | version = "0.52.6"
557 | source = "registry+https://github.com/rust-lang/crates.io-index"
558 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
559 | dependencies = [
560 | "windows_aarch64_gnullvm",
561 | "windows_aarch64_msvc",
562 | "windows_i686_gnu",
563 | "windows_i686_gnullvm",
564 | "windows_i686_msvc",
565 | "windows_x86_64_gnu",
566 | "windows_x86_64_gnullvm",
567 | "windows_x86_64_msvc",
568 | ]
569 |
570 | [[package]]
571 | name = "windows_aarch64_gnullvm"
572 | version = "0.52.6"
573 | source = "registry+https://github.com/rust-lang/crates.io-index"
574 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
575 |
576 | [[package]]
577 | name = "windows_aarch64_msvc"
578 | version = "0.52.6"
579 | source = "registry+https://github.com/rust-lang/crates.io-index"
580 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
581 |
582 | [[package]]
583 | name = "windows_i686_gnu"
584 | version = "0.52.6"
585 | source = "registry+https://github.com/rust-lang/crates.io-index"
586 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
587 |
588 | [[package]]
589 | name = "windows_i686_gnullvm"
590 | version = "0.52.6"
591 | source = "registry+https://github.com/rust-lang/crates.io-index"
592 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
593 |
594 | [[package]]
595 | name = "windows_i686_msvc"
596 | version = "0.52.6"
597 | source = "registry+https://github.com/rust-lang/crates.io-index"
598 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
599 |
600 | [[package]]
601 | name = "windows_x86_64_gnu"
602 | version = "0.52.6"
603 | source = "registry+https://github.com/rust-lang/crates.io-index"
604 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
605 |
606 | [[package]]
607 | name = "windows_x86_64_gnullvm"
608 | version = "0.52.6"
609 | source = "registry+https://github.com/rust-lang/crates.io-index"
610 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
611 |
612 | [[package]]
613 | name = "windows_x86_64_msvc"
614 | version = "0.52.6"
615 | source = "registry+https://github.com/rust-lang/crates.io-index"
616 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
617 |
618 | [[package]]
619 | name = "winnow"
620 | version = "0.7.13"
621 | source = "registry+https://github.com/rust-lang/crates.io-index"
622 | checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
623 |
--------------------------------------------------------------------------------
/src/pointer/slice.rs:
--------------------------------------------------------------------------------
1 | use super::Pointer;
2 | use crate::Token;
3 | use core::ops::Bound;
4 |
5 | pub trait PointerIndex<'p>: private::Sealed {
6 | type Output: 'p;
7 |
8 | fn get(self, pointer: &'p Pointer) -> Option;
9 | }
10 |
11 | impl<'p> PointerIndex<'p> for usize {
12 | type Output = Token<'p>;
13 |
14 | fn get(self, pointer: &'p Pointer) -> Option {
15 | pointer.tokens().nth(self)
16 | }
17 | }
18 |
19 | impl<'p> PointerIndex<'p> for core::ops::Range {
20 | type Output = &'p Pointer;
21 |
22 | fn get(self, pointer: &'p Pointer) -> Option {
23 | if self.end < self.start {
24 | // never valid
25 | return None;
26 | }
27 |
28 | let mut idx = 0;
29 | let mut offset = 0;
30 | let mut start_offset = None;
31 | let mut end_offset = None;
32 |
33 | for token in pointer.tokens() {
34 | if idx == self.start {
35 | start_offset = Some(offset);
36 | }
37 | if idx == self.end {
38 | end_offset = Some(offset);
39 | break;
40 | }
41 | idx += 1;
42 | // also include the `/` separator
43 | offset += token.encoded().len() + 1;
44 | }
45 |
46 | // edge case where end is last token index + 1
47 | // this is valid because range is exclusive
48 | if idx == self.end {
49 | end_offset = Some(offset);
50 | }
51 |
52 | let slice = &pointer.0.as_bytes()[start_offset?..end_offset?];
53 | // SAFETY: start and end offsets are token boundaries, so the slice is
54 | // valid utf-8 (and also a valid json pointer!)
55 | Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
56 | }
57 | }
58 |
59 | impl<'p> PointerIndex<'p> for core::ops::RangeFrom {
60 | type Output = &'p Pointer;
61 |
62 | fn get(self, pointer: &'p Pointer) -> Option {
63 | {
64 | let mut offset = 0;
65 | let mut start_offset = None;
66 |
67 | for (idx, token) in pointer.tokens().enumerate() {
68 | if idx == self.start {
69 | start_offset = Some(offset);
70 | break;
71 | }
72 | // also include the `/` separator
73 | offset += token.encoded().len() + 1;
74 | }
75 |
76 | let slice = &pointer.0.as_bytes()[start_offset?..];
77 | // SAFETY: start offset is token boundary, so the slice is valid
78 | // utf-8 (and also a valid json pointer!)
79 | Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
80 | }
81 | }
82 | }
83 |
84 | impl<'p> PointerIndex<'p> for core::ops::RangeTo {
85 | type Output = &'p Pointer;
86 |
87 | fn get(self, pointer: &'p Pointer) -> Option {
88 | {
89 | let mut idx = 0;
90 | let mut offset = 0;
91 | let mut end_offset = None;
92 |
93 | for token in pointer.tokens() {
94 | if idx == self.end {
95 | end_offset = Some(offset);
96 | break;
97 | }
98 | idx += 1;
99 | // also include the `/` separator
100 | offset += token.encoded().len() + 1;
101 | }
102 |
103 | // edge case where end is last token index + 1
104 | // this is valid because range is exclusive
105 | if idx == self.end {
106 | end_offset = Some(offset);
107 | }
108 |
109 | let slice = &pointer.0.as_bytes()[..end_offset?];
110 | // SAFETY: start and end offsets are token boundaries, so the slice is
111 | // valid utf-8 (and also a valid json pointer!)
112 | Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
113 | }
114 | }
115 | }
116 |
117 | impl<'p> PointerIndex<'p> for core::ops::RangeFull {
118 | type Output = &'p Pointer;
119 |
120 | fn get(self, pointer: &'p Pointer) -> Option {
121 | Some(pointer)
122 | }
123 | }
124 |
125 | impl<'p> PointerIndex<'p> for core::ops::RangeInclusive {
126 | type Output = &'p Pointer;
127 |
128 | fn get(self, pointer: &'p Pointer) -> Option {
129 | let (start, end) = self.into_inner();
130 | if end < start {
131 | // never valid
132 | return None;
133 | }
134 |
135 | let mut offset = 0;
136 | let mut start_offset = None;
137 | let mut end_offset = None;
138 |
139 | for (idx, token) in pointer.tokens().enumerate() {
140 | if idx == start {
141 | start_offset = Some(offset);
142 | }
143 | // also include the `/` separator
144 | offset += token.encoded().len() + 1;
145 | // since the range is inclusive, we wish to slice up until the end
146 | // of the token whose index is `end`, so we increment offset first
147 | // before checking for a match
148 | if idx == end {
149 | end_offset = Some(offset);
150 | break;
151 | }
152 | }
153 |
154 | // notice that we don't use an inclusive range here, because we already
155 | // acounted for the included end token when computing `end_offset` above
156 | let slice = &pointer.0.as_bytes()[start_offset?..end_offset?];
157 | // SAFETY: start and end offsets are token boundaries, so the slice is
158 | // valid utf-8 (and also a valid json pointer!)
159 | Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
160 | }
161 | }
162 |
163 | impl<'p> PointerIndex<'p> for core::ops::RangeToInclusive {
164 | type Output = &'p Pointer;
165 |
166 | fn get(self, pointer: &'p Pointer) -> Option {
167 | {
168 | let mut offset = 0;
169 | let mut end_offset = None;
170 |
171 | for (idx, token) in pointer.tokens().enumerate() {
172 | // also include the `/` separator
173 | offset += token.encoded().len() + 1;
174 | // since the range is inclusive, we wish to slice up until the end
175 | // of the token whose index is `end`, so we increment offset first
176 | // before checking for a match
177 | if idx == self.end {
178 | end_offset = Some(offset);
179 | break;
180 | }
181 | }
182 |
183 | // notice that we don't use an inclusive range here, because we already
184 | // acounted for the included end token when computing `end_offset` above
185 | let slice = &pointer.0.as_bytes()[..end_offset?];
186 | // SAFETY: start and end offsets are token boundaries, so the slice is
187 | // valid utf-8 (and also a valid json pointer!)
188 | Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
189 | }
190 | }
191 | }
192 |
193 | impl<'p> PointerIndex<'p> for (Bound, Bound) {
194 | type Output = &'p Pointer;
195 |
196 | fn get(self, pointer: &'p Pointer) -> Option {
197 | match self {
198 | (Bound::Included(start), Bound::Included(end)) => pointer.get(start..=end),
199 | (Bound::Included(start), Bound::Excluded(end)) => pointer.get(start..end),
200 | (Bound::Included(start), Bound::Unbounded) => pointer.get(start..),
201 | (Bound::Excluded(start), Bound::Included(end)) => pointer.get(start + 1..=end),
202 | (Bound::Excluded(start), Bound::Excluded(end)) => pointer.get(start + 1..end),
203 | (Bound::Excluded(start), Bound::Unbounded) => pointer.get(start + 1..),
204 | (Bound::Unbounded, Bound::Included(end)) => pointer.get(..=end),
205 | (Bound::Unbounded, Bound::Excluded(end)) => pointer.get(..end),
206 | (Bound::Unbounded, Bound::Unbounded) => pointer.get(..),
207 | }
208 | }
209 | }
210 |
211 | mod private {
212 | use core::ops;
213 |
214 | pub trait Sealed {}
215 | impl Sealed for usize {}
216 | impl Sealed for ops::Range {}
217 | impl Sealed for ops::RangeTo {}
218 | impl Sealed for ops::RangeFrom {}
219 | impl Sealed for ops::RangeFull {}
220 | impl Sealed for ops::RangeInclusive {}
221 | impl Sealed for ops::RangeToInclusive {}
222 | impl Sealed for (ops::Bound, ops::Bound) {}
223 | }
224 |
225 | #[cfg(test)]
226 | mod tests {
227 | use core::ops::Bound;
228 |
229 | use crate::{Pointer, Token};
230 |
231 | #[test]
232 | fn get_single() {
233 | let ptr = Pointer::from_static("/foo/bar/qux");
234 | let s = ptr.get(0);
235 | assert_eq!(s, Some(Token::new("foo")));
236 | let s = ptr.get(1);
237 | assert_eq!(s, Some(Token::new("bar")));
238 | let s = ptr.get(2);
239 | assert_eq!(s, Some(Token::new("qux")));
240 | let s = ptr.get(3);
241 | assert_eq!(s, None);
242 |
243 | let ptr = Pointer::from_static("/");
244 | let s = ptr.get(0);
245 | assert_eq!(s, Some(Token::new("")));
246 | let s = ptr.get(1);
247 | assert_eq!(s, None);
248 |
249 | let ptr = Pointer::from_static("");
250 | let s = ptr.get(0);
251 | assert_eq!(s, None);
252 | let s = ptr.get(1);
253 | assert_eq!(s, None);
254 | }
255 |
256 | #[allow(clippy::reversed_empty_ranges)]
257 | #[test]
258 | fn get_range() {
259 | let ptr = Pointer::from_static("/foo/bar/qux");
260 | let s = ptr.get(0..3);
261 | assert_eq!(s, Some(ptr));
262 | let s = ptr.get(0..2);
263 | assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
264 | let s = ptr.get(0..1);
265 | assert_eq!(s, Some(Pointer::from_static("/foo")));
266 | let s = ptr.get(0..0);
267 | assert_eq!(s, Some(Pointer::from_static("")));
268 | let s = ptr.get(1..3);
269 | assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
270 | let s = ptr.get(1..2);
271 | assert_eq!(s, Some(Pointer::from_static("/bar")));
272 | let s = ptr.get(1..1);
273 | assert_eq!(s, Some(Pointer::from_static("")));
274 | let s = ptr.get(1..0);
275 | assert_eq!(s, None);
276 | let s = ptr.get(0..4);
277 | assert_eq!(s, None);
278 | let s = ptr.get(2..4);
279 | assert_eq!(s, None);
280 |
281 | let ptr = Pointer::from_static("/");
282 | let s = ptr.get(0..1);
283 | assert_eq!(s, Some(ptr));
284 | let s = ptr.get(0..0);
285 | assert_eq!(s, Some(Pointer::root()));
286 | let s = ptr.get(1..0);
287 | assert_eq!(s, None);
288 | let s = ptr.get(0..2);
289 | assert_eq!(s, None);
290 | let s = ptr.get(1..2);
291 | assert_eq!(s, None);
292 | let s = ptr.get(1..1);
293 | assert_eq!(s, None);
294 |
295 | let ptr = Pointer::root();
296 | let s = ptr.get(0..1);
297 | assert_eq!(s, None);
298 | let s = ptr.get(0..0);
299 | assert_eq!(s, None);
300 | let s = ptr.get(1..0);
301 | assert_eq!(s, None);
302 | let s = ptr.get(1..1);
303 | assert_eq!(s, None);
304 | }
305 |
306 | #[test]
307 | fn get_from_range() {
308 | let ptr = Pointer::from_static("/foo/bar/qux");
309 | let s = ptr.get(0..);
310 | assert_eq!(s, Some(ptr));
311 | let s = ptr.get(1..);
312 | assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
313 | let s = ptr.get(2..);
314 | assert_eq!(s, Some(Pointer::from_static("/qux")));
315 | let s = ptr.get(3..);
316 | assert_eq!(s, None);
317 |
318 | let ptr = Pointer::from_static("/");
319 | let s = ptr.get(0..);
320 | assert_eq!(s, Some(ptr));
321 | let s = ptr.get(1..);
322 | assert_eq!(s, None);
323 |
324 | let ptr = Pointer::from_static("");
325 | let s = ptr.get(0..);
326 | assert_eq!(s, None);
327 | }
328 |
329 | #[test]
330 | fn get_to_range() {
331 | let ptr = Pointer::from_static("/foo/bar/qux");
332 | let s = ptr.get(..4);
333 | assert_eq!(s, None);
334 | let s = ptr.get(..3);
335 | assert_eq!(s, Some(ptr));
336 | let s = ptr.get(..2);
337 | assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
338 | let s = ptr.get(..1);
339 | assert_eq!(s, Some(Pointer::from_static("/foo")));
340 | let s = ptr.get(..0);
341 | assert_eq!(s, Some(Pointer::from_static("")));
342 |
343 | let ptr = Pointer::from_static("/");
344 | let s = ptr.get(..0);
345 | assert_eq!(s, Some(Pointer::from_static("")));
346 | let s = ptr.get(..1);
347 | assert_eq!(s, Some(ptr));
348 | let s = ptr.get(..2);
349 | assert_eq!(s, None);
350 |
351 | let ptr = Pointer::from_static("");
352 | let s = ptr.get(..0);
353 | assert_eq!(s, Some(ptr));
354 | let s = ptr.get(..1);
355 | assert_eq!(s, None);
356 | }
357 |
358 | #[test]
359 | fn get_full_range() {
360 | let ptr = Pointer::from_static("/foo/bar");
361 | let s = ptr.get(..);
362 | assert_eq!(s, Some(ptr));
363 |
364 | let ptr = Pointer::from_static("/");
365 | let s = ptr.get(..);
366 | assert_eq!(s, Some(ptr));
367 |
368 | let ptr = Pointer::from_static("");
369 | let s = ptr.get(..);
370 | assert_eq!(s, Some(ptr));
371 | }
372 |
373 | #[allow(clippy::reversed_empty_ranges)]
374 | #[test]
375 | fn get_range_inclusive() {
376 | let ptr = Pointer::from_static("/foo/bar/qux");
377 | let s = ptr.get(0..=3);
378 | assert_eq!(s, None);
379 | let s = ptr.get(0..=2);
380 | assert_eq!(s, Some(ptr));
381 | let s = ptr.get(0..=1);
382 | assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
383 | let s = ptr.get(0..=0);
384 | assert_eq!(s, Some(Pointer::from_static("/foo")));
385 | let s = ptr.get(1..=3);
386 | assert_eq!(s, None);
387 | let s = ptr.get(1..=2);
388 | assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
389 | let s = ptr.get(1..=1);
390 | assert_eq!(s, Some(Pointer::from_static("/bar")));
391 | let s = ptr.get(1..=0);
392 | assert_eq!(s, None);
393 |
394 | let ptr = Pointer::from_static("/");
395 | let s = ptr.get(0..=0);
396 | assert_eq!(s, Some(ptr));
397 | let s = ptr.get(1..=0);
398 | assert_eq!(s, None);
399 | let s = ptr.get(0..=1);
400 | assert_eq!(s, None);
401 | let s = ptr.get(1..=1);
402 | assert_eq!(s, None);
403 |
404 | let ptr = Pointer::root();
405 | let s = ptr.get(0..=1);
406 | assert_eq!(s, None);
407 | let s = ptr.get(0..=0);
408 | assert_eq!(s, None);
409 | let s = ptr.get(1..=0);
410 | assert_eq!(s, None);
411 | let s = ptr.get(1..=1);
412 | assert_eq!(s, None);
413 | }
414 |
415 | #[test]
416 | fn get_to_range_inclusive() {
417 | let ptr = Pointer::from_static("/foo/bar/qux");
418 | let s = ptr.get(..=3);
419 | assert_eq!(s, None);
420 | let s = ptr.get(..=2);
421 | assert_eq!(s, Some(ptr));
422 | let s = ptr.get(..=1);
423 | assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
424 | let s = ptr.get(..=0);
425 | assert_eq!(s, Some(Pointer::from_static("/foo")));
426 |
427 | let ptr = Pointer::from_static("/");
428 | let s = ptr.get(..=0);
429 | assert_eq!(s, Some(ptr));
430 | let s = ptr.get(..=1);
431 | assert_eq!(s, None);
432 |
433 | let ptr = Pointer::from_static("");
434 | let s = ptr.get(..=0);
435 | assert_eq!(s, None);
436 | let s = ptr.get(..=1);
437 | assert_eq!(s, None);
438 | }
439 |
440 | #[test]
441 | fn get_by_explicit_bounds() {
442 | let ptr = Pointer::from_static("/foo/bar/qux");
443 | let s = ptr.get((Bound::Excluded(0), Bound::Included(2)));
444 | assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
445 | let s = ptr.get((Bound::Excluded(0), Bound::Excluded(2)));
446 | assert_eq!(s, Some(Pointer::from_static("/bar")));
447 | let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
448 | assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
449 | let s = ptr.get((Bound::Included(0), Bound::Included(2)));
450 | assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
451 | let s = ptr.get((Bound::Included(0), Bound::Excluded(2)));
452 | assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
453 | let s = ptr.get((Bound::Included(0), Bound::Unbounded));
454 | assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
455 | let s = ptr.get((Bound::Unbounded, Bound::Included(2)));
456 | assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
457 | let s = ptr.get((Bound::Unbounded, Bound::Excluded(2)));
458 | assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
459 | let s = ptr.get((Bound::Unbounded, Bound::Unbounded));
460 | assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
461 |
462 | let ptr = Pointer::from_static("/foo/bar");
463 | let s = ptr.get((Bound::Excluded(0), Bound::Included(2)));
464 | assert_eq!(s, None);
465 | let s = ptr.get((Bound::Excluded(0), Bound::Excluded(2)));
466 | assert_eq!(s, Some(Pointer::from_static("/bar")));
467 | let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
468 | assert_eq!(s, Some(Pointer::from_static("/bar")));
469 | let s = ptr.get((Bound::Included(0), Bound::Included(2)));
470 | assert_eq!(s, None);
471 | let s = ptr.get((Bound::Included(0), Bound::Excluded(2)));
472 | assert_eq!(s, Some(ptr));
473 | let s = ptr.get((Bound::Included(0), Bound::Unbounded));
474 | assert_eq!(s, Some(ptr));
475 | let s = ptr.get((Bound::Unbounded, Bound::Included(2)));
476 | assert_eq!(s, None);
477 | let s = ptr.get((Bound::Unbounded, Bound::Excluded(2)));
478 | assert_eq!(s, Some(ptr));
479 | let s = ptr.get((Bound::Unbounded, Bound::Unbounded));
480 | assert_eq!(s, Some(ptr));
481 |
482 | // testing only the start excluded case a bit more exhaustively since
483 | // other cases just delegate directly (so are covered by other tests)
484 | let ptr = Pointer::from_static("/");
485 | let s = ptr.get((Bound::Excluded(0), Bound::Included(0)));
486 | assert_eq!(s, None);
487 | let s = ptr.get((Bound::Excluded(0), Bound::Excluded(0)));
488 | assert_eq!(s, None);
489 | let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
490 | assert_eq!(s, None);
491 |
492 | let ptr = Pointer::from_static("");
493 | let s = ptr.get((Bound::Excluded(0), Bound::Included(0)));
494 | assert_eq!(s, None);
495 | let s = ptr.get((Bound::Excluded(0), Bound::Excluded(0)));
496 | assert_eq!(s, None);
497 | let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
498 | assert_eq!(s, None);
499 | }
500 | }
501 |
--------------------------------------------------------------------------------
/src/index.rs:
--------------------------------------------------------------------------------
1 | //! Abstract index representation for RFC 6901.
2 | //!
3 | //! [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) defines two valid
4 | //! ways to represent array indices as Pointer tokens: non-negative integers,
5 | //! and the character `-`, which stands for the index after the last existing
6 | //! array member. While attempting to use `-` to resolve an array value will
7 | //! always be out of bounds, the token can be useful when paired with utilities
8 | //! which can mutate a value, such as this crate's [`assign`](crate::assign)
9 | //! functionality or JSON Patch [RFC
10 | //! 6902](https://datatracker.ietf.org/doc/html/rfc6902), as it provides a way
11 | //! to express where to put the new element when extending an array.
12 | //!
13 | //! While this crate doesn't implement RFC 6902, it still must consider
14 | //! non-numerical indices as valid, and provide a mechanism for manipulating
15 | //! them. This is what this module provides.
16 | //!
17 | //! The main use of the `Index` type is when resolving a [`Token`] instance as a
18 | //! concrete index for a given array length:
19 | //!
20 | //! ```
21 | //! # use jsonptr::{index::Index, Token};
22 | //! assert_eq!(Token::new("1").to_index(), Ok(Index::Num(1)));
23 | //! assert_eq!(Token::new("-").to_index(), Ok(Index::Next));
24 | //! assert!(Token::new("a").to_index().is_err());
25 | //!
26 | //! assert_eq!(Index::Num(0).for_len(1), Ok(0));
27 | //! assert!(Index::Num(1).for_len(1).is_err());
28 | //! assert!(Index::Next.for_len(1).is_err());
29 | //!
30 | //! assert_eq!(Index::Num(1).for_len_incl(1), Ok(1));
31 | //! assert_eq!(Index::Next.for_len_incl(1), Ok(1));
32 | //! assert!(Index::Num(2).for_len_incl(1).is_err());
33 | //!
34 | //! assert_eq!(Index::Num(42).for_len_unchecked(30), 42);
35 | //! assert_eq!(Index::Next.for_len_unchecked(30), 30);
36 | //! ```
37 |
38 | use crate::{
39 | diagnostic::{diagnostic_url, Diagnostic, Label},
40 | Token,
41 | };
42 | use alloc::{boxed::Box, string::String};
43 | use core::{fmt, iter::once, num::ParseIntError, str::FromStr};
44 |
45 | /// Represents an abstract index into an array.
46 | ///
47 | /// If provided an upper bound with [`Self::for_len`] or [`Self::for_len_incl`],
48 | /// will produce a concrete numerical index.
49 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50 | pub enum Index {
51 | /// A non-negative integer value
52 | Num(usize),
53 | /// The `-` token, the position of the next would-be item in the array
54 | Next,
55 | }
56 |
57 | impl Index {
58 | /// Bounds the index for a given array length (exclusive).
59 | ///
60 | /// The upper range is exclusive, so only indices that are less than
61 | /// the given length will be accepted as valid. This ensures that
62 | /// the resolved numerical index can be used to access an existing array
63 | /// element.
64 | ///
65 | /// [`Self::Next`], by consequence, is always considered *invalid*, since
66 | /// it resolves to the array length itself.
67 | ///
68 | /// See also [`Self::for_len_incl`] for an alternative if you wish to accept
69 | /// [`Self::Next`] (or its numerical equivalent) as valid.
70 | ///
71 | /// # Examples
72 | ///
73 | /// ```
74 | /// # use jsonptr::index::Index;
75 | /// assert_eq!(Index::Num(0).for_len(1), Ok(0));
76 | /// assert!(Index::Num(1).for_len(1).is_err());
77 | /// assert!(Index::Next.for_len(1).is_err());
78 | /// ```
79 | /// # Errors
80 | /// Returns [`OutOfBoundsError`] if the index is out of bounds.
81 | pub fn for_len(&self, length: usize) -> Result {
82 | match *self {
83 | Self::Num(index) if index < length => Ok(index),
84 | Self::Num(index) => Err(OutOfBoundsError { length, index }),
85 | Self::Next => Err(OutOfBoundsError {
86 | length,
87 | index: length,
88 | }),
89 | }
90 | }
91 |
92 | /// Bounds the index for a given array length (inclusive).
93 | ///
94 | /// The upper range is inclusive, so an index pointing to the position
95 | /// _after_ the last element will be considered valid. Be careful when using
96 | /// the resulting numerical index for accessing an array.
97 | ///
98 | /// [`Self::Next`] is always considered valid.
99 | ///
100 | /// See also [`Self::for_len`] for an alternative if you wish to ensure that
101 | /// the resolved index can be used to access an existing array element.
102 | ///
103 | /// # Examples
104 | ///
105 | /// ```
106 | /// # use jsonptr::index::Index;
107 | /// assert_eq!(Index::Num(1).for_len_incl(1), Ok(1));
108 | /// assert_eq!(Index::Next.for_len_incl(1), Ok(1));
109 | /// assert!(Index::Num(2).for_len_incl(1).is_err());
110 | /// ```
111 | ///
112 | /// # Errors
113 | /// Returns [`OutOfBoundsError`] if the index is out of bounds.
114 | pub fn for_len_incl(&self, length: usize) -> Result {
115 | match *self {
116 | Self::Num(index) if index <= length => Ok(index),
117 | Self::Num(index) => Err(OutOfBoundsError { length, index }),
118 | Self::Next => Ok(length),
119 | }
120 | }
121 |
122 | /// Resolves the index for a given array length.
123 | ///
124 | /// No bound checking will take place. If you wish to ensure the
125 | /// index can be used to access an existing element in the array, use
126 | /// [`Self::for_len`] - or use [`Self::for_len_incl`] if you wish to accept
127 | /// [`Self::Next`] as valid as well.
128 | ///
129 | /// # Examples
130 | ///
131 | /// ```
132 | /// # use jsonptr::index::Index;
133 | /// assert_eq!(Index::Num(42).for_len_unchecked(30), 42);
134 | /// assert_eq!(Index::Next.for_len_unchecked(30), 30);
135 | ///
136 | /// // no bounds checks
137 | /// assert_eq!(Index::Num(34).for_len_unchecked(40), 34);
138 | /// assert_eq!(Index::Next.for_len_unchecked(34), 34);
139 | /// ```
140 | pub fn for_len_unchecked(&self, length: usize) -> usize {
141 | match *self {
142 | Self::Num(idx) => idx,
143 | Self::Next => length,
144 | }
145 | }
146 | }
147 |
148 | impl fmt::Display for Index {
149 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150 | match *self {
151 | Self::Num(index) => write!(f, "{index}"),
152 | Self::Next => f.write_str("-"),
153 | }
154 | }
155 | }
156 |
157 | impl From for Index {
158 | fn from(value: usize) -> Self {
159 | Self::Num(value)
160 | }
161 | }
162 |
163 | impl FromStr for Index {
164 | type Err = ParseIndexError;
165 |
166 | fn from_str(s: &str) -> Result {
167 | if s == "-" {
168 | Ok(Index::Next)
169 | } else if s.starts_with('0') && s != "0" {
170 | Err(ParseIndexError::LeadingZeros)
171 | } else {
172 | s.chars().position(|c| !c.is_ascii_digit()).map_or_else(
173 | || {
174 | s.parse::()
175 | .map(Index::Num)
176 | .map_err(ParseIndexError::from)
177 | },
178 | |offset| {
179 | // this comes up with the `+` sign which is valid for
180 | // representing a `usize` but not allowed in RFC 6901 array
181 | // indices
182 | Err(ParseIndexError::InvalidCharacter(InvalidCharacterError {
183 | offset,
184 | }))
185 | },
186 | )
187 | }
188 | }
189 | }
190 |
191 | impl TryFrom<&Token<'_>> for Index {
192 | type Error = ParseIndexError;
193 |
194 | fn try_from(value: &Token) -> Result {
195 | Index::from_str(value.encoded())
196 | }
197 | }
198 |
199 | impl TryFrom<&str> for Index {
200 | type Error = ParseIndexError;
201 |
202 | fn try_from(value: &str) -> Result {
203 | Index::from_str(value)
204 | }
205 | }
206 |
207 | impl TryFrom> for Index {
208 | type Error = ParseIndexError;
209 |
210 | fn try_from(value: Token) -> Result {
211 | Index::from_str(value.encoded())
212 | }
213 | }
214 |
215 | macro_rules! derive_try_from {
216 | ($($t:ty),+ $(,)?) => {
217 | $(
218 | impl TryFrom<$t> for Index {
219 | type Error = ParseIndexError;
220 |
221 | fn try_from(value: $t) -> Result {
222 | Index::from_str(&value)
223 | }
224 | }
225 | )*
226 | }
227 | }
228 |
229 | derive_try_from!(String, &String);
230 |
231 | /*
232 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
233 | ╔══════════════════════════════════════════════════════════════════════════════╗
234 | ║ ║
235 | ║ OutOfBoundsError ║
236 | ║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
237 | ╚══════════════════════════════════════════════════════════════════════════════╝
238 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
239 | */
240 |
241 | /// Indicates that an `Index` is not within the given bounds.
242 | #[derive(Debug, Clone, PartialEq, Eq)]
243 | pub struct OutOfBoundsError {
244 | /// The provided array length.
245 | ///
246 | /// If the range is inclusive, the resolved numerical index will be strictly
247 | /// less than this value, otherwise it could be equal to it.
248 | pub length: usize,
249 |
250 | /// The resolved numerical index.
251 | ///
252 | /// Note that [`Index::Next`] always resolves to the given array length,
253 | /// so it is only valid when the range is inclusive.
254 | pub index: usize,
255 | }
256 |
257 | impl fmt::Display for OutOfBoundsError {
258 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 | write!(
260 | f,
261 | "index {} out of bounds (len: {})",
262 | self.index, self.length
263 | )
264 | }
265 | }
266 |
267 | #[cfg(feature = "std")]
268 | impl std::error::Error for OutOfBoundsError {}
269 |
270 | /*
271 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
272 | ╔══════════════════════════════════════════════════════════════════════════════╗
273 | ║ ║
274 | ║ ParseIndexError ║
275 | ║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
276 | ╚══════════════════════════════════════════════════════════════════════════════╝
277 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
278 | */
279 |
280 | /// Indicates that the `Token` could not be parsed as valid RFC 6901 array index.
281 | #[derive(Debug, Clone, PartialEq, Eq)]
282 | pub enum ParseIndexError {
283 | /// The Token does not represent a valid integer.
284 | InvalidInteger(ParseIntError),
285 | /// The Token contains leading zeros.
286 | LeadingZeros,
287 | /// The Token contains a non-digit character.
288 | InvalidCharacter(InvalidCharacterError),
289 | }
290 |
291 | impl From for ParseIndexError {
292 | fn from(source: ParseIntError) -> Self {
293 | Self::InvalidInteger(source)
294 | }
295 | }
296 |
297 | impl fmt::Display for ParseIndexError {
298 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 | match self {
300 | ParseIndexError::InvalidInteger(_) => {
301 | write!(f, "failed to parse token as an integer")
302 | }
303 | ParseIndexError::LeadingZeros => write!(
304 | f,
305 | "token contained leading zeros, which are disallowed by RFC 6901"
306 | ),
307 | ParseIndexError::InvalidCharacter(_) => {
308 | write!(f, "failed to parse token as an index")
309 | }
310 | }
311 | }
312 | }
313 |
314 | // shouldn't be used directly, but is part of a public interface
315 | #[doc(hidden)]
316 | #[derive(Debug)]
317 | pub enum StringOrToken {
318 | String(String),
319 | Token(Token<'static>),
320 | }
321 |
322 | impl From for StringOrToken {
323 | fn from(value: String) -> Self {
324 | Self::String(value)
325 | }
326 | }
327 |
328 | impl From> for StringOrToken {
329 | fn from(value: Token<'static>) -> Self {
330 | Self::Token(value)
331 | }
332 | }
333 |
334 | impl core::ops::Deref for StringOrToken {
335 | type Target = str;
336 |
337 | fn deref(&self) -> &Self::Target {
338 | match self {
339 | StringOrToken::String(s) => s.as_str(),
340 | StringOrToken::Token(t) => t.encoded(),
341 | }
342 | }
343 | }
344 |
345 | #[cfg(feature = "miette")]
346 | impl miette::SourceCode for StringOrToken {
347 | fn read_span<'a>(
348 | &'a self,
349 | span: &miette::SourceSpan,
350 | context_lines_before: usize,
351 | context_lines_after: usize,
352 | ) -> Result + 'a>, miette::MietteError> {
353 | let s: &str = &**self;
354 | s.read_span(span, context_lines_before, context_lines_after)
355 | }
356 | }
357 |
358 | impl Diagnostic for ParseIndexError {
359 | type Subject = StringOrToken;
360 |
361 | fn url() -> &'static str {
362 | diagnostic_url!(enum ParseIndexError)
363 | }
364 |
365 | fn labels(
366 | &self,
367 | subject: &Self::Subject,
368 | ) -> Option>> {
369 | let subject = &**subject;
370 | match self {
371 | ParseIndexError::InvalidInteger(_) => None,
372 | ParseIndexError::LeadingZeros => {
373 | let len = subject
374 | .chars()
375 | .position(|c| c != '0')
376 | .expect("starts with zeros");
377 | let text = String::from("leading zeros");
378 | Some(Box::new(once(Label::new(text, 0, len))))
379 | }
380 | ParseIndexError::InvalidCharacter(err) => {
381 | let len = subject
382 | .chars()
383 | .skip(err.offset)
384 | .position(|c| c.is_ascii_digit())
385 | .unwrap_or(subject.len());
386 | let text = String::from("invalid character(s)");
387 | Some(Box::new(once(Label::new(text, err.offset, len))))
388 | }
389 | }
390 | }
391 | }
392 |
393 | #[cfg(feature = "miette")]
394 | impl miette::Diagnostic for ParseIndexError {}
395 |
396 | #[cfg(feature = "std")]
397 | impl std::error::Error for ParseIndexError {
398 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
399 | match self {
400 | ParseIndexError::InvalidInteger(source) => Some(source),
401 | ParseIndexError::InvalidCharacter(source) => Some(source),
402 | ParseIndexError::LeadingZeros => None,
403 | }
404 | }
405 | }
406 |
407 | /// Indicates that a non-digit character was found when parsing the RFC 6901 array index.
408 | #[derive(Debug, Clone, PartialEq, Eq)]
409 | pub struct InvalidCharacterError {
410 | pub(crate) offset: usize,
411 | }
412 |
413 | impl InvalidCharacterError {
414 | /// Returns the offset of the character in the string.
415 | ///
416 | /// This offset is given in characters, not in bytes.
417 | pub fn offset(&self) -> usize {
418 | self.offset
419 | }
420 | }
421 |
422 | impl fmt::Display for InvalidCharacterError {
423 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 | write!(
425 | f,
426 | "token contains a non-digit character, \
427 | which is disallowed by RFC 6901",
428 | )
429 | }
430 | }
431 |
432 | #[cfg(feature = "std")]
433 | impl std::error::Error for InvalidCharacterError {}
434 |
435 | /*
436 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
437 | ╔══════════════════════════════════════════════════════════════════════════════╗
438 | ║ ║
439 | ║ Tests ║
440 | ║ ¯¯¯¯¯¯¯ ║
441 | ╚══════════════════════════════════════════════════════════════════════════════╝
442 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
443 | */
444 |
445 | #[cfg(test)]
446 | mod tests {
447 | use super::*;
448 | use crate::{Diagnose, Token};
449 |
450 | #[test]
451 | fn index_from_usize() {
452 | let index = Index::from(5usize);
453 | assert_eq!(index, Index::Num(5));
454 | }
455 |
456 | #[test]
457 | fn index_try_from_token_num() {
458 | let token = Token::new("3");
459 | let index = Index::try_from(&token).unwrap();
460 | assert_eq!(index, Index::Num(3));
461 | }
462 |
463 | #[test]
464 | fn index_try_from_token_next() {
465 | let token = Token::new("-");
466 | let index = Index::try_from(&token).unwrap();
467 | assert_eq!(index, Index::Next);
468 | }
469 |
470 | #[test]
471 | fn index_try_from_str_num() {
472 | let index = Index::try_from("42").unwrap();
473 | assert_eq!(index, Index::Num(42));
474 | }
475 |
476 | #[test]
477 | fn index_try_from_str_next() {
478 | let index = Index::try_from("-").unwrap();
479 | assert_eq!(index, Index::Next);
480 | }
481 |
482 | #[test]
483 | fn index_try_from_string_num() {
484 | let index = Index::try_from(String::from("7")).unwrap();
485 | assert_eq!(index, Index::Num(7));
486 | }
487 |
488 | #[test]
489 | fn index_try_from_string_next() {
490 | let index = Index::try_from(String::from("-")).unwrap();
491 | assert_eq!(index, Index::Next);
492 | }
493 |
494 | #[test]
495 | fn index_for_len_incl_valid() {
496 | assert_eq!(Index::Num(0).for_len_incl(1), Ok(0));
497 | assert_eq!(Index::Next.for_len_incl(2), Ok(2));
498 | }
499 |
500 | #[test]
501 | fn index_for_len_incl_out_of_bounds() {
502 | Index::Num(2).for_len_incl(1).unwrap_err();
503 | }
504 |
505 | #[test]
506 | fn index_for_len_unchecked() {
507 | assert_eq!(Index::Num(10).for_len_unchecked(5), 10);
508 | assert_eq!(Index::Next.for_len_unchecked(3), 3);
509 | }
510 |
511 | #[test]
512 | fn display_index_num() {
513 | let index = Index::Num(5);
514 | assert_eq!(index.to_string(), "5");
515 | }
516 |
517 | #[test]
518 | fn display_index_next() {
519 | assert_eq!(Index::Next.to_string(), "-");
520 | }
521 |
522 | #[test]
523 | fn for_len() {
524 | assert_eq!(Index::Num(0).for_len(1), Ok(0));
525 | assert!(Index::Num(1).for_len(1).is_err());
526 | assert!(Index::Next.for_len(1).is_err());
527 | }
528 |
529 | #[test]
530 | fn try_from_token() {
531 | let token = Token::new("3");
532 | let index = >::try_from(token).unwrap();
533 | assert_eq!(index, Index::Num(3));
534 | let token = Token::new("-");
535 | let index = Index::try_from(&token).unwrap();
536 | assert_eq!(index, Index::Next);
537 | }
538 |
539 | #[test]
540 | fn diagnose_works_with_token_or_string() {
541 | let token = Token::new("foo");
542 | // despite the clone, this is cheap because `token` is borrowed
543 | Index::try_from(token.clone()).diagnose(token).unwrap_err();
544 | let s = String::from("bar");
545 | Index::try_from(&s).diagnose(s).unwrap_err();
546 | }
547 |
548 | #[test]
549 | fn error_from_invalid_chars() {
550 | let s = String::from("bar");
551 | let err = Index::try_from(&s).diagnose(s).unwrap_err();
552 |
553 | #[cfg(feature = "miette")]
554 | {
555 | let labels: Vec<_> = miette::Diagnostic::labels(&err)
556 | .unwrap()
557 | .into_iter()
558 | .collect();
559 | assert_eq!(
560 | labels,
561 | vec![miette::LabeledSpan::new(
562 | Some("invalid character(s)".into()),
563 | 0,
564 | 3
565 | )]
566 | );
567 | }
568 |
569 | let (src, sub) = err.decompose();
570 | let labels: Vec<_> = src.labels(&sub).unwrap().into_iter().collect();
571 |
572 | assert_eq!(
573 | labels,
574 | vec![Label::new("invalid character(s)".into(), 0, 3)]
575 | );
576 | }
577 |
578 | #[test]
579 | fn error_from_leading_zeros() {
580 | let s = String::from("000001");
581 | let err = Index::try_from(&s).diagnose(s).unwrap_err();
582 |
583 | #[cfg(feature = "miette")]
584 | {
585 | let labels: Vec<_> = miette::Diagnostic::labels(&err)
586 | .unwrap()
587 | .into_iter()
588 | .collect();
589 | assert_eq!(
590 | labels,
591 | vec![miette::LabeledSpan::new(Some("leading zeros".into()), 0, 5)]
592 | );
593 | }
594 |
595 | let (src, sub) = err.decompose();
596 | let labels: Vec<_> = src.labels(&sub).unwrap().into_iter().collect();
597 |
598 | assert_eq!(labels, vec![Label::new("leading zeros".into(), 0, 5)]);
599 | }
600 |
601 | #[test]
602 | fn error_from_empty_string() {
603 | let s = String::from("");
604 | let err = Index::try_from(&s).diagnose(s).unwrap_err();
605 |
606 | #[cfg(feature = "miette")]
607 | {
608 | assert!(miette::Diagnostic::labels(&err).is_none());
609 | }
610 |
611 | let (src, sub) = err.decompose();
612 | assert!(src.labels(&sub).is_none());
613 | }
614 | }
615 |
--------------------------------------------------------------------------------
/src/token.rs:
--------------------------------------------------------------------------------
1 | use core::{iter::once, str::Split};
2 |
3 | use crate::{
4 | diagnostic::{diagnostic_url, Diagnostic, Label},
5 | index::{Index, ParseIndexError},
6 | };
7 | use alloc::{
8 | borrow::Cow,
9 | boxed::Box,
10 | fmt,
11 | string::{String, ToString},
12 | vec::Vec,
13 | };
14 |
15 | const ENCODED_TILDE: &[u8] = b"~0";
16 | const ENCODED_SLASH: &[u8] = b"~1";
17 |
18 | const ENC_PREFIX: u8 = b'~';
19 | const TILDE_ENC: u8 = b'0';
20 | const SLASH_ENC: u8 = b'1';
21 |
22 | /*
23 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
24 | ╔══════════════════════════════════════════════════════════════════════════════╗
25 | ║ ║
26 | ║ Token ║
27 | ║ ¯¯¯¯¯¯¯ ║
28 | ╚══════════════════════════════════════════════════════════════════════════════╝
29 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
30 | */
31 |
32 | /// A `Token` is a segment of a JSON [`Pointer`](crate::Token), preceded by `'/'` (`%x2F`).
33 | ///
34 | /// `Token`s can represent a key in a JSON object or an index in an array.
35 | ///
36 | /// - Indexes should not contain leading zeros.
37 | /// - When dealing with arrays or path expansion for assignment, `"-"` represent
38 | /// the next, non-existent index in a JSON array.
39 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40 | pub struct Token<'a> {
41 | inner: Cow<'a, str>,
42 | }
43 |
44 | impl<'a> Token<'a> {
45 | /// Constructs a `Token` from an RFC 6901 encoded string.
46 | ///
47 | /// This is like [`Self::from_encoded`], except that no validation is
48 | /// performed on the input string.
49 | ///
50 | /// ## Safety
51 | /// Input string must be RFC 6901 encoded.
52 | pub(crate) unsafe fn from_encoded_unchecked(inner: impl Into>) -> Self {
53 | Self {
54 | inner: inner.into(),
55 | }
56 | }
57 |
58 | /// Constructs a `Token` from an RFC 6901 encoded string.
59 | ///
60 | /// To be valid, the string must not contain any `/` characters, and any `~`
61 | /// characters must be followed by either `0` or `1`.
62 | ///
63 | /// This function does not allocate.
64 | ///
65 | /// # Examples
66 | ///
67 | /// ```
68 | /// # use jsonptr::Token;
69 | /// assert_eq!(Token::from_encoded("~1foo~1~0bar").unwrap().decoded(), "/foo/~bar");
70 | /// let err = Token::from_encoded("foo/oops~bar").unwrap_err();
71 | /// assert_eq!(err.offset, 3);
72 | /// ```
73 | ///
74 | /// ## Errors
75 | /// Returns `InvalidEncodingError` if the input string is not a valid RFC
76 | /// 6901 (`~` must be followed by `0` or `1`)
77 | pub fn from_encoded(s: impl Into>) -> Result {
78 | let inner = s.into();
79 | let mut escaped = false;
80 | for (offset, b) in inner.bytes().enumerate() {
81 | match b {
82 | b'/' => {
83 | return Err(EncodingError {
84 | offset,
85 | source: InvalidEncoding::Slash,
86 | })
87 | }
88 | ENC_PREFIX => {
89 | escaped = true;
90 | }
91 | TILDE_ENC | SLASH_ENC if escaped => {
92 | escaped = false;
93 | }
94 | _ => {
95 | if escaped {
96 | return Err(EncodingError {
97 | offset,
98 | source: InvalidEncoding::Tilde,
99 | });
100 | }
101 | }
102 | }
103 | }
104 | if escaped {
105 | return Err(EncodingError {
106 | offset: inner.len(),
107 | source: InvalidEncoding::Tilde,
108 | });
109 | }
110 | Ok(Self { inner })
111 | }
112 |
113 | /// Constructs a `Token` from an arbitrary string.
114 | ///
115 | /// If the string contains a `/` or a `~`, then it will be assumed not
116 | /// encoded, in which case this function will encode it, allocating a new
117 | /// string.
118 | ///
119 | /// If the string is already encoded per RFC 6901, use
120 | /// [`Self::from_encoded`] instead, otherwise it will end up double-encoded.
121 | ///
122 | /// # Examples
123 | ///
124 | /// ```
125 | /// # use jsonptr::Token;
126 | /// assert_eq!(Token::new("/foo/~bar").encoded(), "~1foo~1~0bar");
127 | /// ```
128 | pub fn new(s: impl Into>) -> Self {
129 | let s = s.into();
130 |
131 | if let Some(i) = s.bytes().position(|b| b == b'/' || b == b'~') {
132 | let input = s.as_bytes();
133 | // we could take advantage of [`Cow::into_owned`] here, but it would
134 | // mean copying over the entire string, only to overwrite a portion
135 | // of it... so instead we explicitly allocate a new buffer and copy
136 | // only the prefix until the first encoded character
137 | // NOTE: the output is at least as large as the input + 1, so we
138 | // allocate that much capacity ahead of time
139 | let mut bytes = Vec::with_capacity(input.len() + 1);
140 | bytes.extend_from_slice(&input[..i]);
141 | for &b in &input[i..] {
142 | match b {
143 | b'/' => {
144 | bytes.extend_from_slice(ENCODED_SLASH);
145 | }
146 | b'~' => {
147 | bytes.extend_from_slice(ENCODED_TILDE);
148 | }
149 | other => {
150 | bytes.push(other);
151 | }
152 | }
153 | }
154 | Self {
155 | // SAFETY: we started from a valid UTF-8 sequence of bytes,
156 | // and only replaced some ASCII characters with other two ASCII
157 | // characters, so the output is guaranteed valid UTF-8.
158 | inner: Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) }),
159 | }
160 | } else {
161 | Self { inner: s }
162 | }
163 | }
164 |
165 | /// Converts into an owned copy of this token.
166 | ///
167 | /// If the token is not already owned, this will clone the referenced string
168 | /// slice.
169 | pub fn into_owned(self) -> Token<'static> {
170 | Token {
171 | inner: Cow::Owned(self.inner.into_owned()),
172 | }
173 | }
174 |
175 | /// Extracts an owned copy of this token.
176 | ///
177 | /// If the token is not already owned, this will clone the referenced string
178 | /// slice.
179 | ///
180 | /// This method is like [`Self::into_owned`], except it doesn't take
181 | /// ownership of the original `Token`.
182 | pub fn to_owned(&self) -> Token<'static> {
183 | Token {
184 | inner: Cow::Owned(self.inner.clone().into_owned()),
185 | }
186 | }
187 |
188 | /// Returns the encoded string representation of the `Token`.
189 | ///
190 | /// # Examples
191 | ///
192 | /// ```
193 | /// # use jsonptr::Token;
194 | /// assert_eq!(Token::new("~bar").encoded(), "~0bar");
195 | /// ```
196 | pub fn encoded(&self) -> &str {
197 | &self.inner
198 | }
199 |
200 | /// Returns the decoded string representation of the `Token`.
201 | ///
202 | /// # Examples
203 | ///
204 | /// ```
205 | /// # use jsonptr::Token;
206 | /// assert_eq!(Token::new("~bar").decoded(), "~bar");
207 | /// ```
208 | pub fn decoded(&self) -> Cow<'_, str> {
209 | if let Some(i) = self.inner.bytes().position(|b| b == ENC_PREFIX) {
210 | let input = self.inner.as_bytes();
211 | // we could take advantage of [`Cow::into_owned`] here, but it would
212 | // mean copying over the entire string, only to overwrite a portion
213 | // of it... so instead we explicitly allocate a new buffer and copy
214 | // only the prefix until the first encoded character
215 | // NOTE: the output is at least as large as the input + 1, so we
216 | // allocate that much capacity ahead of time
217 | let mut bytes = Vec::with_capacity(input.len() + 1);
218 | bytes.extend_from_slice(&input[..i]);
219 | // we start from the first escaped character
220 | let mut escaped = true;
221 | for &b in &input[i + 1..] {
222 | match b {
223 | ENC_PREFIX => {
224 | escaped = true;
225 | }
226 | TILDE_ENC if escaped => {
227 | bytes.push(b'~');
228 | escaped = false;
229 | }
230 | SLASH_ENC if escaped => {
231 | bytes.push(b'/');
232 | escaped = false;
233 | }
234 | other => {
235 | bytes.push(other);
236 | }
237 | }
238 | }
239 | // SAFETY: we start from a valid String, and only write valid UTF-8
240 | // byte sequences into it.
241 | Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) })
242 | } else {
243 | // if there are no encoded characters, we don't need to allocate!
244 | self.inner.clone()
245 | }
246 | }
247 |
248 | /// Attempts to parse the given `Token` as an array index.
249 | ///
250 | /// Per [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901#section-4),
251 | /// the acceptable values are non-negative integers and the `-` character,
252 | /// which stands for the next, non-existent member after the last array
253 | /// element.
254 | ///
255 | /// ## Examples
256 | ///
257 | /// ```
258 | /// # use jsonptr::{index::Index, Token};
259 | /// assert_eq!(Token::new("-").to_index(), Ok(Index::Next));
260 | /// assert_eq!(Token::new("0").to_index(), Ok(Index::Num(0)));
261 | /// assert_eq!(Token::new("2").to_index(), Ok(Index::Num(2)));
262 | /// assert!(Token::new("a").to_index().is_err());
263 | /// assert!(Token::new("-1").to_index().is_err());
264 | /// ```
265 | /// ## Errors
266 | /// Returns [`ParseIndexError`] if the token is not a valid array index.
267 | pub fn to_index(&self) -> Result {
268 | self.try_into()
269 | }
270 |
271 | /// Returns if the `Token` is `-`, which stands for the next array index.
272 | ///
273 | /// See also [`Self::to_index`].
274 | pub fn is_next(&self) -> bool {
275 | matches!(self.to_index(), Ok(Index::Next))
276 | }
277 | }
278 |
279 | macro_rules! impl_from_num {
280 | ($($ty:ty),*) => {
281 | $(
282 | impl From<$ty> for Token<'static> {
283 | fn from(v: $ty) -> Self {
284 | // SAFETY: only used for integer types, which are always valid
285 | unsafe { Token::from_encoded_unchecked(v.to_string()) }
286 | }
287 | }
288 | )*
289 | };
290 | }
291 | impl_from_num!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
292 |
293 | impl<'a> From<&'a str> for Token<'a> {
294 | fn from(value: &'a str) -> Self {
295 | Token::new(value)
296 | }
297 | }
298 |
299 | impl<'a> From<&'a String> for Token<'a> {
300 | fn from(value: &'a String) -> Self {
301 | Token::new(value)
302 | }
303 | }
304 |
305 | impl From for Token<'static> {
306 | fn from(value: String) -> Self {
307 | Token::new(value)
308 | }
309 | }
310 |
311 | impl<'a> From<&Token<'a>> for Token<'a> {
312 | fn from(value: &Token<'a>) -> Self {
313 | value.clone()
314 | }
315 | }
316 |
317 | impl alloc::fmt::Display for Token<'_> {
318 | fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
319 | write!(f, "{}", self.decoded())
320 | }
321 | }
322 |
323 | /*
324 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
325 | ╔══════════════════════════════════════════════════════════════════════════════╗
326 | ║ ║
327 | ║ Tokens ║
328 | ║ ¯¯¯¯¯¯¯¯ ║
329 | ╚══════════════════════════════════════════════════════════════════════════════╝
330 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
331 | */
332 |
333 | /// An iterator over the [`Token`]s of a [`Pointer`](crate::Pointer).
334 | #[derive(Debug)]
335 | pub struct Tokens<'a> {
336 | inner: Split<'a, char>,
337 | }
338 |
339 | impl<'a> Iterator for Tokens<'a> {
340 | type Item = Token<'a>;
341 | fn next(&mut self) -> Option {
342 | self.inner
343 | .next()
344 | // SAFETY: source pointer is encoded
345 | .map(|s| unsafe { Token::from_encoded_unchecked(s) })
346 | }
347 | }
348 | impl<'t> Tokens<'t> {
349 | pub(crate) fn new(inner: Split<'t, char>) -> Self {
350 | Self { inner }
351 | }
352 | }
353 |
354 | /*
355 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
356 | ╔══════════════════════════════════════════════════════════════════════════════╗
357 | ║ ║
358 | ║ InvalidEncodingError ║
359 | ║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
360 | ╚══════════════════════════════════════════════════════════════════════════════╝
361 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
362 | */
363 |
364 | #[deprecated(since = "0.7.0", note = "renamed to `EncodingError`")]
365 | /// Deprecated alias for [`EncodingError`].
366 | pub type InvalidEncodingError = EncodingError;
367 |
368 | /*
369 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
370 | ╔══════════════════════════════════════════════════════════════════════════════╗
371 | ║ ║
372 | ║ EncodingError ║
373 | ║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
374 | ╚══════════════════════════════════════════════════════════════════════════════╝
375 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
376 | */
377 |
378 | /// A token within a json pointer contained invalid encoding (`~` not followed
379 | /// by `0` or `1`).
380 | ///
381 | #[derive(Debug, PartialEq, Eq)]
382 | pub struct EncodingError {
383 | /// offset of the erroneous `~` from within the `Token`
384 | pub offset: usize,
385 | /// the specific encoding error
386 | pub source: InvalidEncoding,
387 | }
388 |
389 | impl fmt::Display for EncodingError {
390 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 | write!(
392 | f,
393 | "token contains invalid encoding at offset {}",
394 | self.offset
395 | )
396 | }
397 | }
398 |
399 | impl Diagnostic for EncodingError {
400 | type Subject = String;
401 |
402 | fn url() -> &'static str {
403 | diagnostic_url!(struct EncodingError)
404 | }
405 |
406 | fn labels(&self, subject: &Self::Subject) -> Option>> {
407 | let (text, offset) = match self.source {
408 | InvalidEncoding::Tilde => {
409 | if self.offset == subject.len() {
410 | ("incomplete escape sequence", self.offset - 1)
411 | } else {
412 | ("must be 0 or 1", self.offset)
413 | }
414 | }
415 | InvalidEncoding::Slash => ("invalid character", self.offset),
416 | };
417 | Some(Box::new(once(Label::new(text.to_string(), offset, 1))))
418 | }
419 | }
420 |
421 | #[cfg(feature = "miette")]
422 | impl miette::Diagnostic for EncodingError {}
423 |
424 | #[cfg(feature = "std")]
425 | impl std::error::Error for EncodingError {
426 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
427 | Some(&self.source)
428 | }
429 | }
430 |
431 | /*
432 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
433 | ╔══════════════════════════════════════════════════════════════════════════════╗
434 | ║ ║
435 | ║ InvalidEncoding ║
436 | ║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
437 | ╚══════════════════════════════════════════════════════════════════════════════╝
438 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
439 | */
440 |
441 | /// Represents the specific type of invalid encoding error.
442 | #[derive(Debug, PartialEq, Eq, Clone, Copy)]
443 | pub enum InvalidEncoding {
444 | /// `~` not followed by `0` or `1`
445 | Tilde,
446 | /// non-encoded `/` found in token
447 | Slash,
448 | }
449 |
450 | impl fmt::Display for InvalidEncoding {
451 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452 | match self {
453 | InvalidEncoding::Tilde => write!(f, "tilde (~) not followed by 0 or 1"),
454 | InvalidEncoding::Slash => write!(f, "slash (/) found in token"),
455 | }
456 | }
457 | }
458 |
459 | #[cfg(feature = "std")]
460 | impl std::error::Error for InvalidEncoding {}
461 |
462 | /*
463 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
464 | ╔══════════════════════════════════════════════════════════════════════════════╗
465 | ║ ║
466 | ║ Tests ║
467 | ║ ¯¯¯¯¯¯¯ ║
468 | ╚══════════════════════════════════════════════════════════════════════════════╝
469 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
470 | */
471 |
472 | #[cfg(test)]
473 | mod tests {
474 | use crate::Pointer;
475 |
476 | use super::*;
477 | use quickcheck_macros::quickcheck;
478 |
479 | #[test]
480 | fn from() {
481 | assert_eq!(Token::from("/").encoded(), "~1");
482 | assert_eq!(Token::from("~/").encoded(), "~0~1");
483 | assert_eq!(Token::from(34u32).encoded(), "34");
484 | assert_eq!(Token::from(34u64).encoded(), "34");
485 | assert_eq!(Token::from(String::from("foo")).encoded(), "foo");
486 | assert_eq!(Token::from(&Token::new("foo")).encoded(), "foo");
487 | }
488 |
489 | #[test]
490 | fn to_index() {
491 | assert_eq!(Token::new("-").to_index(), Ok(Index::Next));
492 | assert_eq!(Token::new("0").to_index(), Ok(Index::Num(0)));
493 | assert_eq!(Token::new("2").to_index(), Ok(Index::Num(2)));
494 | assert!(Token::new("a").to_index().is_err());
495 | assert!(Token::new("-1").to_index().is_err());
496 | }
497 |
498 | #[test]
499 | fn new() {
500 | assert_eq!(Token::new("~1").encoded(), "~01");
501 | assert_eq!(Token::new("a/b").encoded(), "a~1b");
502 | }
503 |
504 | #[test]
505 | fn from_encoded() {
506 | assert_eq!(Token::from_encoded("~1").unwrap().encoded(), "~1");
507 | assert_eq!(Token::from_encoded("~0~1").unwrap().encoded(), "~0~1");
508 | let t = Token::from_encoded("a~1b").unwrap();
509 | assert_eq!(t.decoded(), "a/b");
510 |
511 | let sub = String::from("a/b");
512 | let err = Token::from_encoded(&sub).unwrap_err();
513 | let labels: Vec<_> = err.labels(&sub).unwrap().into_iter().collect();
514 | assert_eq!(labels, vec![Label::new("invalid character".into(), 1, 1)]);
515 | let err = err.into_report(sub);
516 | #[cfg(feature = "miette")]
517 | {
518 | let labels: Vec<_> = miette::Diagnostic::labels(&err)
519 | .unwrap()
520 | .into_iter()
521 | .collect();
522 | assert_eq!(
523 | labels,
524 | vec![miette::LabeledSpan::new(
525 | Some("invalid character".into()),
526 | 1,
527 | 1
528 | )]
529 | );
530 | }
531 | let (err, _) = err.decompose();
532 | assert_eq!(
533 | err,
534 | EncodingError {
535 | offset: 1,
536 | source: InvalidEncoding::Slash
537 | }
538 | );
539 |
540 | let sub = String::from("a~a");
541 | let err = Token::from_encoded(&sub).unwrap_err();
542 | let labels: Vec<_> = err.labels(&sub).unwrap().into_iter().collect();
543 | assert_eq!(labels, vec![Label::new("must be 0 or 1".into(), 2, 1)]);
544 | let err = err.into_report(sub);
545 | #[cfg(feature = "miette")]
546 | {
547 | let labels: Vec<_> = miette::Diagnostic::labels(&err)
548 | .unwrap()
549 | .into_iter()
550 | .collect();
551 | assert_eq!(
552 | labels,
553 | vec![miette::LabeledSpan::new(
554 | Some("must be 0 or 1".into()),
555 | 2,
556 | 1
557 | )]
558 | );
559 | }
560 | let (err, _) = err.decompose();
561 | assert_eq!(
562 | err,
563 | EncodingError {
564 | offset: 2,
565 | source: InvalidEncoding::Tilde
566 | }
567 | );
568 | let sub = String::from("a~");
569 | let err = Token::from_encoded(&sub).unwrap_err();
570 | let labels: Vec<_> = err.labels(&sub).unwrap().into_iter().collect();
571 | assert_eq!(
572 | labels,
573 | vec![Label::new("incomplete escape sequence".into(), 1, 1)]
574 | );
575 | let err = err.into_report(sub);
576 | #[cfg(feature = "miette")]
577 | {
578 | let labels: Vec<_> = miette::Diagnostic::labels(&err)
579 | .unwrap()
580 | .into_iter()
581 | .collect();
582 | assert_eq!(
583 | labels,
584 | vec![miette::LabeledSpan::new(
585 | Some("incomplete escape sequence".into()),
586 | 1,
587 | 1
588 | )]
589 | );
590 | }
591 | let (err, _) = err.decompose();
592 | assert_eq!(
593 | err,
594 | EncodingError {
595 | offset: 2,
596 | source: InvalidEncoding::Tilde
597 | }
598 | );
599 | }
600 |
601 | #[test]
602 | fn into_owned() {
603 | let token = Token::from_encoded("foo~0").unwrap().into_owned();
604 | assert_eq!(token.encoded(), "foo~0");
605 | }
606 |
607 | #[quickcheck]
608 | fn encode_decode(s: String) -> bool {
609 | let token = Token::new(s);
610 | let decoded = Token::from_encoded(token.encoded()).unwrap();
611 | token == decoded
612 | }
613 |
614 | #[test]
615 | fn tokens() {
616 | let pointer = Pointer::from_static("/a/b/c");
617 | let tokens: Vec = pointer.tokens().collect();
618 | assert_eq!(tokens, unsafe {
619 | vec![
620 | Token::from_encoded_unchecked("a"),
621 | Token::from_encoded_unchecked("b"),
622 | Token::from_encoded_unchecked("c"),
623 | ]
624 | });
625 | }
626 |
627 | #[test]
628 | fn is_next() {
629 | let token = Token::new("-");
630 | assert!(token.is_next());
631 | let token = Token::new("0");
632 | assert!(!token.is_next());
633 | let token = Token::new("a");
634 | assert!(!token.is_next());
635 | let token = Token::new("");
636 | assert!(!token.is_next());
637 | }
638 | }
639 |
--------------------------------------------------------------------------------
/src/resolve.rs:
--------------------------------------------------------------------------------
1 | //! # Resolve values based on JSON [`Pointer`]s
2 | //!
3 | //! This module provides the [`Resolve`] and [`ResolveMut`] traits which are
4 | //! implemented by types that can internally resolve a value based on a JSON
5 | //! Pointer.
6 | //!
7 | //! This module is enabled by default with the `"resolve"` feature flag.
8 | //!
9 | //! ## Usage
10 | //! [`Resolve`] and [`ResolveMut`] can be used directly or through the
11 | //! [`resolve`](Pointer::resolve) and [`resolve_mut`](Pointer::resolve_mut)
12 | //! methods on [`Pointer`] and [`PointerBuf`](crate::PointerBuf).
13 | //!
14 | //! ```rust
15 | //! use jsonptr::{Pointer, Resolve, ResolveMut};
16 | //! use serde_json::json;
17 | //!
18 | //! let ptr = Pointer::from_static("/foo/1");
19 | //! let mut data = json!({"foo": ["bar", "baz"]});
20 | //!
21 | //! let value = ptr.resolve(&data).unwrap();
22 | //! assert_eq!(value, &json!("baz"));
23 | //!
24 | //! let value = data.resolve_mut(ptr).unwrap();
25 | //! assert_eq!(value, &json!("baz"));
26 | //! ```
27 | //!
28 | //! ## Provided implementations
29 | //!
30 | //! | Lang | value type | feature flag | Default |
31 | //! | ----- |: ----------------- :|: ---------- :| ------- |
32 | //! | JSON | `serde_json::Value` | `"json"` | ✓ |
33 | //! | TOML | `toml::Value` | `"toml"` | |
34 | //!
35 | //!
36 | use crate::{
37 | diagnostic::{diagnostic_url, Diagnostic, Label},
38 | index::{OutOfBoundsError, ParseIndexError},
39 | Pointer, PointerBuf, Token,
40 | };
41 | use alloc::{boxed::Box, string::ToString};
42 | use core::iter::once;
43 |
44 | /// A trait implemented by types which can resolve a reference to a value type
45 | /// from a path represented by a JSON [`Pointer`].
46 | pub trait Resolve {
47 | /// The type of value that this implementation can operate on.
48 | type Value;
49 |
50 | /// Error associated with `Resolve`
51 | type Error;
52 |
53 | /// Resolve a reference to `Self::Value` based on the path in a [Pointer].
54 | ///
55 | /// ## Errors
56 | /// Returns a [`Self::Error`](Resolve::Error) if the [`Pointer`] can not
57 | /// be resolved.
58 | fn resolve(&self, ptr: &Pointer) -> Result<&Self::Value, Self::Error>;
59 | }
60 |
61 | /// A trait implemented by types which can resolve a mutable reference to a
62 | /// value type from a path represented by a JSON [`Pointer`].
63 | pub trait ResolveMut {
64 | /// The type of value that is being resolved.
65 | type Value;
66 |
67 | /// Error associated with `ResolveMut`
68 | type Error;
69 |
70 | /// Resolve a mutable reference to a `serde_json::Value` based on the path
71 | /// in a JSON Pointer.
72 | ///
73 | /// ## Errors
74 | /// Returns a [`Self::Error`](ResolveMut::Error) if the [`Pointer`] can not
75 | /// be resolved.
76 | fn resolve_mut(&mut self, ptr: &Pointer) -> Result<&mut Self::Value, Self::Error>;
77 | }
78 |
79 | /// Alias for [`Error`].
80 | #[deprecated(since = "0.7.2", note = "renamed to `Error`")]
81 | pub type ResolveError = Error;
82 |
83 | /// Indicates that the `Pointer` could not be resolved.
84 | #[derive(Debug, PartialEq, Eq)]
85 | pub enum Error {
86 | /// `Pointer` could not be resolved because a `Token` for an array index is
87 | /// not a valid integer or dash (`"-"`).
88 | ///
89 | /// ## Example
90 | /// ```rust
91 | /// # use serde_json::json;
92 | /// # use jsonptr::Pointer;
93 | /// let data = json!({ "foo": ["bar"] });
94 | /// let ptr = Pointer::from_static("/foo/invalid");
95 | /// assert!(ptr.resolve(&data).unwrap_err().is_failed_to_parse_index());
96 | /// ```
97 | FailedToParseIndex {
98 | /// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index)
99 | position: usize,
100 | /// Offset of the partial pointer starting with the invalid index.
101 | offset: usize,
102 | /// The source `ParseIndexError`
103 | source: ParseIndexError,
104 | },
105 |
106 | /// A [`Token`] within the [`Pointer`] contains an [`Index`] which is out of
107 | /// bounds.
108 | ///
109 | /// ## Example
110 | /// ```rust
111 | /// # use serde_json::json;
112 | /// # use jsonptr::Pointer;
113 | /// let data = json!({ "foo": ["bar"] });
114 | /// let ptr = Pointer::from_static("/foo/1");
115 | /// assert!(ptr.resolve(&data).unwrap_err().is_out_of_bounds());
116 | OutOfBounds {
117 | /// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index)
118 | position: usize,
119 | /// Offset of the partial pointer starting with the invalid index.
120 | offset: usize,
121 | /// The source `OutOfBoundsError`
122 | source: OutOfBoundsError,
123 | },
124 |
125 | /// `Pointer` could not be resolved as a segment of the path was not found.
126 | ///
127 | /// ## Example
128 | /// ```rust
129 | /// # use serde_json::json;
130 | /// # use jsonptr::{Pointer};
131 | /// let mut data = json!({ "foo": "bar" });
132 | /// let ptr = Pointer::from_static("/bar");
133 | /// assert!(ptr.resolve(&data).unwrap_err().is_not_found());
134 | /// ```
135 | NotFound {
136 | /// Position (index) of the token which was not found.
137 | position: usize,
138 | /// Offset of the pointer starting with the `Token` which was not found.
139 | offset: usize,
140 | },
141 |
142 | /// `Pointer` could not be resolved as the path contains a scalar value
143 | /// before fully exhausting the path.
144 | ///
145 | /// ## Example
146 | /// ```rust
147 | /// # use serde_json::json;
148 | /// # use jsonptr::Pointer;
149 | /// let mut data = json!({ "foo": "bar" });
150 | /// let ptr = Pointer::from_static("/foo/unreachable");
151 | /// let err = ptr.resolve(&data).unwrap_err();
152 | /// assert!(err.is_unreachable());
153 | /// ```
154 | Unreachable {
155 | /// Position (index) of the token which was unreachable.
156 | position: usize,
157 | /// Offset of the pointer which was unreachable.
158 | offset: usize,
159 | },
160 | }
161 |
162 | impl Error {
163 | /// Offset of the partial pointer starting with the token which caused the
164 | /// error.
165 | pub fn offset(&self) -> usize {
166 | match self {
167 | Self::FailedToParseIndex { offset, .. }
168 | | Self::OutOfBounds { offset, .. }
169 | | Self::NotFound { offset, .. }
170 | | Self::Unreachable { offset, .. } => *offset,
171 | }
172 | }
173 |
174 | /// Position (index) of the token which caused the error.
175 | pub fn position(&self) -> usize {
176 | match self {
177 | Self::FailedToParseIndex { position, .. }
178 | | Self::OutOfBounds { position, .. }
179 | | Self::NotFound { position, .. }
180 | | Self::Unreachable { position, .. } => *position,
181 | }
182 | }
183 |
184 | /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
185 | /// `false`.
186 | pub fn is_unreachable(&self) -> bool {
187 | matches!(self, Self::Unreachable { .. })
188 | }
189 |
190 | /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
191 | /// `false`.
192 | pub fn is_not_found(&self) -> bool {
193 | matches!(self, Self::NotFound { .. })
194 | }
195 |
196 | /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
197 | /// `false`.
198 | pub fn is_out_of_bounds(&self) -> bool {
199 | matches!(self, Self::OutOfBounds { .. })
200 | }
201 |
202 | /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
203 | /// `false`.
204 | pub fn is_failed_to_parse_index(&self) -> bool {
205 | matches!(self, Self::FailedToParseIndex { .. })
206 | }
207 | }
208 |
209 | impl Diagnostic for Error {
210 | type Subject = PointerBuf;
211 |
212 | fn url() -> &'static str {
213 | diagnostic_url!(enum assign::Error)
214 | }
215 |
216 | fn labels(&self, origin: &Self::Subject) -> Option>> {
217 | let position = self.position();
218 | let token = origin.get(position)?;
219 | let offset = if self.offset() + 1 < origin.as_str().len() {
220 | self.offset() + 1
221 | } else {
222 | self.offset()
223 | };
224 | let len = token.encoded().len();
225 | let text = match self {
226 | Error::FailedToParseIndex { .. } => "not an array index".to_string(),
227 | Error::OutOfBounds { source, .. } => source.to_string(),
228 | Error::NotFound { .. } => "not found in value".to_string(),
229 | Error::Unreachable { .. } => "unreachable".to_string(),
230 | };
231 | Some(Box::new(once(Label::new(text, offset, len))))
232 | }
233 | }
234 |
235 | impl core::fmt::Display for Error {
236 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237 | match self {
238 | Self::FailedToParseIndex { offset, .. } => {
239 | write!(f, "resolve failed: json pointer token at offset {offset} failed to parse as an index")
240 | }
241 | Self::OutOfBounds { offset, .. } => {
242 | write!(
243 | f,
244 | "resolve failed: json pointer token at offset {offset} is out of bounds"
245 | )
246 | }
247 | Self::NotFound { offset, .. } => {
248 | write!(
249 | f,
250 | "resolve failed: json pointer token at {offset} was not found in value"
251 | )
252 | }
253 | Self::Unreachable { offset, .. } => {
254 | write!(f, "resolve failed: json pointer token at {offset} is unreachable (previous token resolved to a scalar or null value)")
255 | }
256 | }
257 | }
258 | }
259 |
260 | #[cfg(feature = "std")]
261 | impl std::error::Error for Error {
262 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
263 | match self {
264 | Self::FailedToParseIndex { source, .. } => Some(source),
265 | Self::OutOfBounds { source, .. } => Some(source),
266 | _ => None,
267 | }
268 | }
269 | }
270 |
271 | #[cfg(feature = "json")]
272 | mod json {
273 | use super::{parse_index, Error, Pointer, Resolve, ResolveMut};
274 | use serde_json::Value;
275 |
276 | impl Resolve for Value {
277 | type Value = Value;
278 | type Error = Error;
279 |
280 | fn resolve(&self, mut ptr: &Pointer) -> Result<&Value, Self::Error> {
281 | let mut offset = 0;
282 | let mut position = 0;
283 | let mut value = self;
284 | while let Some((token, rem)) = ptr.split_front() {
285 | let tok_len = token.encoded().len();
286 | ptr = rem;
287 | value = match value {
288 | Value::Array(v) => {
289 | let idx = token
290 | .to_index()
291 | .map_err(|source| Error::FailedToParseIndex {
292 | position,
293 | offset,
294 | source,
295 | })?
296 | .for_len(v.len())
297 | .map_err(|source| Error::OutOfBounds {
298 | position,
299 | offset,
300 | source,
301 | })?;
302 | Ok(&v[idx])
303 | }
304 |
305 | Value::Object(v) => v
306 | .get(token.decoded().as_ref())
307 | .ok_or(Error::NotFound { position, offset }),
308 | // found a leaf node but the pointer hasn't been exhausted
309 | _ => Err(Error::Unreachable { position, offset }),
310 | }?;
311 | offset += 1 + tok_len;
312 | position += 1;
313 | }
314 | Ok(value)
315 | }
316 | }
317 |
318 | impl ResolveMut for Value {
319 | type Value = Value;
320 | type Error = Error;
321 |
322 | fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, Error> {
323 | let mut offset = 0;
324 | let mut position = 0;
325 | let mut value = self;
326 | while let Some((token, rem)) = ptr.split_front() {
327 | let tok_len = token.encoded().len();
328 | ptr = rem;
329 | value = match value {
330 | Value::Array(array) => {
331 | let idx = parse_index(token, array.len(), position, offset)?;
332 | Ok(&mut array[idx])
333 | }
334 | Value::Object(v) => v
335 | .get_mut(token.decoded().as_ref())
336 | .ok_or(Error::NotFound { position, offset }),
337 | // found a leaf node but the pointer hasn't been exhausted
338 | _ => Err(Error::Unreachable { position, offset }),
339 | }?;
340 | offset += 1 + tok_len;
341 | position += 1;
342 | }
343 | Ok(value)
344 | }
345 | }
346 | }
347 | fn parse_index(
348 | token: Token,
349 | array_len: usize,
350 | position: usize,
351 | offset: usize,
352 | ) -> Result {
353 | token
354 | .to_index()
355 | .map_err(|source| Error::FailedToParseIndex {
356 | position,
357 | offset,
358 | source,
359 | })?
360 | .for_len(array_len)
361 | .map_err(|source| Error::OutOfBounds {
362 | position,
363 | offset,
364 | source,
365 | })
366 | }
367 |
368 | #[cfg(feature = "toml")]
369 | mod toml {
370 | use super::{Error, Resolve, ResolveMut};
371 | use crate::Pointer;
372 | use toml::Value;
373 |
374 | impl Resolve for Value {
375 | type Value = Value;
376 | type Error = Error;
377 |
378 | fn resolve(&self, mut ptr: &Pointer) -> Result<&Value, Self::Error> {
379 | let mut offset = 0;
380 | let mut position = 0;
381 | let mut value = self;
382 | while let Some((token, rem)) = ptr.split_front() {
383 | let tok_len = token.encoded().len();
384 | ptr = rem;
385 | value = match value {
386 | Value::Array(v) => {
387 | let idx = token
388 | .to_index()
389 | .map_err(|source| Error::FailedToParseIndex {
390 | position,
391 | offset,
392 | source,
393 | })?
394 | .for_len(v.len())
395 | .map_err(|source| Error::OutOfBounds {
396 | position,
397 | offset,
398 | source,
399 | })?;
400 | Ok(&v[idx])
401 | }
402 |
403 | Value::Table(v) => v
404 | .get(token.decoded().as_ref())
405 | .ok_or(Error::NotFound { position, offset }),
406 | // found a leaf node but the pointer hasn't been exhausted
407 | _ => Err(Error::Unreachable { position, offset }),
408 | }?;
409 | offset += 1 + tok_len;
410 | position += 1;
411 | }
412 | Ok(value)
413 | }
414 | }
415 |
416 | impl ResolveMut for Value {
417 | type Value = Value;
418 | type Error = Error;
419 |
420 | fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, Error> {
421 | let mut offset = 0;
422 | let mut position = 0;
423 |
424 | let mut value = self;
425 | while let Some((token, rem)) = ptr.split_front() {
426 | let tok_len = token.encoded().len();
427 | ptr = rem;
428 | value = match value {
429 | Value::Array(array) => {
430 | let idx = token
431 | .to_index()
432 | .map_err(|source| Error::FailedToParseIndex {
433 | position,
434 | offset,
435 | source,
436 | })?
437 | .for_len(array.len())
438 | .map_err(|source| Error::OutOfBounds {
439 | position,
440 | offset,
441 | source,
442 | })?;
443 | Ok(&mut array[idx])
444 | }
445 | Value::Table(v) => v
446 | .get_mut(token.decoded().as_ref())
447 | .ok_or(Error::NotFound { position, offset }),
448 | // found a leaf node but the pointer hasn't been exhausted
449 | _ => Err(Error::Unreachable { position, offset }),
450 | }?;
451 | offset += 1 + tok_len;
452 | position += 1;
453 | }
454 | Ok(value)
455 | }
456 | }
457 | }
458 |
459 | #[cfg(test)]
460 | mod tests {
461 | use super::{Error, Resolve, ResolveMut};
462 | use crate::{
463 | index::{OutOfBoundsError, ParseIndexError},
464 | Pointer,
465 | };
466 | use core::fmt;
467 |
468 | #[test]
469 | fn resolve_error_is_unreachable() {
470 | let err = Error::FailedToParseIndex {
471 | position: 0,
472 | offset: 0,
473 | source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()),
474 | };
475 | assert!(!err.is_unreachable());
476 |
477 | let err = Error::OutOfBounds {
478 | position: 0,
479 | offset: 0,
480 | source: OutOfBoundsError {
481 | index: 1,
482 | length: 0,
483 | },
484 | };
485 | assert!(!err.is_unreachable());
486 |
487 | let err = Error::NotFound {
488 | position: 0,
489 | offset: 0,
490 | };
491 | assert!(!err.is_unreachable());
492 |
493 | let err = Error::Unreachable {
494 | position: 0,
495 | offset: 0,
496 | };
497 | assert!(err.is_unreachable());
498 | }
499 |
500 | #[test]
501 | fn resolve_error_is_not_found() {
502 | let err = Error::FailedToParseIndex {
503 | position: 0,
504 | offset: 0,
505 | source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()),
506 | };
507 | assert!(!err.is_not_found());
508 |
509 | let err = Error::OutOfBounds {
510 | position: 0,
511 | offset: 0,
512 | source: OutOfBoundsError {
513 | index: 1,
514 | length: 0,
515 | },
516 | };
517 | assert!(!err.is_not_found());
518 |
519 | let err = Error::NotFound {
520 | position: 0,
521 | offset: 0,
522 | };
523 | assert!(err.is_not_found());
524 |
525 | let err = Error::Unreachable {
526 | position: 0,
527 | offset: 0,
528 | };
529 | assert!(!err.is_not_found());
530 | }
531 |
532 | #[test]
533 | fn resolve_error_is_out_of_bounds() {
534 | let err = Error::FailedToParseIndex {
535 | position: 0,
536 | offset: 0,
537 | source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()),
538 | };
539 | assert!(!err.is_out_of_bounds());
540 |
541 | let err = Error::OutOfBounds {
542 | position: 0,
543 | offset: 0,
544 | source: OutOfBoundsError {
545 | index: 1,
546 | length: 0,
547 | },
548 | };
549 | assert!(err.is_out_of_bounds());
550 |
551 | let err = Error::NotFound {
552 | position: 0,
553 | offset: 0,
554 | };
555 | assert!(!err.is_out_of_bounds());
556 |
557 | let err = Error::Unreachable {
558 | position: 0,
559 | offset: 0,
560 | };
561 | assert!(!err.is_out_of_bounds());
562 | }
563 |
564 | #[test]
565 | fn resolve_error_is_failed_to_parse_index() {
566 | let err = Error::FailedToParseIndex {
567 | position: 0,
568 | offset: 0,
569 | source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()),
570 | };
571 | assert!(err.is_failed_to_parse_index());
572 |
573 | let err = Error::OutOfBounds {
574 | position: 0,
575 | offset: 0,
576 | source: OutOfBoundsError {
577 | index: 1,
578 | length: 0,
579 | },
580 | };
581 | assert!(!err.is_failed_to_parse_index());
582 |
583 | let err = Error::NotFound {
584 | position: 0,
585 | offset: 0,
586 | };
587 | assert!(!err.is_failed_to_parse_index());
588 |
589 | let err = Error::Unreachable {
590 | position: 0,
591 | offset: 0,
592 | };
593 | assert!(!err.is_failed_to_parse_index());
594 | }
595 |
596 | /*
597 | ╔═══════════════════════════════════════════════════╗
598 | ║ json ║
599 | ╚═══════════════════════════════════════════════════╝
600 | */
601 |
602 | #[test]
603 | #[cfg(feature = "json")]
604 | fn resolve_json() {
605 | use serde_json::json;
606 |
607 | let data = &json!({
608 | "array": ["bar", "baz"],
609 | "object": {
610 | "object": {"baz": {"qux": "quux"}},
611 | "strings": ["zero", "one", "two"],
612 | "nothing": null,
613 | "bool": true,
614 | "objects": [{"field": "zero"}, {"field": "one"}, {"field": "two"}]
615 | },
616 | "": 0,
617 | "a/b": 1,
618 | "c%d": 2,
619 | "e^f": 3,
620 | "g|h": 4,
621 | "i\\j": 5,
622 | "k\"l": 6,
623 | " ": 7,
624 | "m~n": 8
625 | });
626 | // let data = &data;
627 |
628 | Test::all([
629 | // 0
630 | Test {
631 | ptr: "",
632 | data,
633 | expected: Ok(data),
634 | },
635 | // 1
636 | Test {
637 | ptr: "/array",
638 | data,
639 | expected: Ok(data.get("array").unwrap()), // ["bar", "baz"]
640 | },
641 | // 2
642 | Test {
643 | ptr: "/array/0",
644 | data,
645 | expected: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar"
646 | },
647 | // 3
648 | Test {
649 | ptr: "/a~1b",
650 | data,
651 | expected: Ok(data.get("a/b").unwrap()), // 1
652 | },
653 | // 4
654 | Test {
655 | ptr: "/c%d",
656 | data,
657 | expected: Ok(data.get("c%d").unwrap()), // 2
658 | },
659 | // 5
660 | Test {
661 | ptr: "/e^f",
662 | data,
663 | expected: Ok(data.get("e^f").unwrap()), // 3
664 | },
665 | // 6
666 | Test {
667 | ptr: "/g|h",
668 | data,
669 | expected: Ok(data.get("g|h").unwrap()), // 4
670 | },
671 | // 7
672 | Test {
673 | ptr: "/i\\j",
674 | data,
675 | expected: Ok(data.get("i\\j").unwrap()), // 5
676 | },
677 | // 8
678 | Test {
679 | ptr: "/k\"l",
680 | data,
681 | expected: Ok(data.get("k\"l").unwrap()), // 6
682 | },
683 | // 9
684 | Test {
685 | ptr: "/ ",
686 | data,
687 | expected: Ok(data.get(" ").unwrap()), // 7
688 | },
689 | // 10
690 | Test {
691 | ptr: "/m~0n",
692 | data,
693 | expected: Ok(data.get("m~n").unwrap()), // 8
694 | },
695 | // 11
696 | Test {
697 | ptr: "/object/bool/unresolvable",
698 | data,
699 | expected: Err(Error::Unreachable {
700 | position: 2,
701 | offset: 12,
702 | }),
703 | },
704 | // 12
705 | Test {
706 | ptr: "/object/not_found",
707 | data,
708 | expected: Err(Error::NotFound {
709 | position: 1,
710 | offset: 7,
711 | }),
712 | },
713 | ]);
714 | }
715 |
716 | /*
717 | ╔═══════════════════════════════════════════════════╗
718 | ║ toml ║
719 | ╚═══════════════════════════════════════════════════╝
720 | */
721 | #[test]
722 | #[cfg(feature = "toml")]
723 | fn resolve_toml() {
724 | use toml::{toml, Value};
725 |
726 | let data = &Value::Table(toml! {
727 | "array" = ["bar", "baz"]
728 | "object" = {
729 | "object" = {"baz" = {"qux" = "quux"}},
730 | "strings" = ["zero", "one", "two"],
731 | "bool" = true,
732 | "objects" = [{"field" = "zero"}, {"field" = "one"}, {"field" = "two"}]
733 | }
734 | "" = 0
735 | "a/b" = 1
736 | "c%d" = 2
737 | "e^f" = 3
738 | "g|h" = 4
739 | "i\\j" = 5
740 | "k\"l" = 6
741 | " " = 7
742 | "m~n" = 8
743 | });
744 | // let data = &data;
745 |
746 | Test::all([
747 | Test {
748 | ptr: "",
749 | data,
750 | expected: Ok(data),
751 | },
752 | Test {
753 | ptr: "/array",
754 | data,
755 | expected: Ok(data.get("array").unwrap()), // ["bar", "baz"]
756 | },
757 | Test {
758 | ptr: "/array/0",
759 | data,
760 | expected: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar"
761 | },
762 | Test {
763 | ptr: "/a~1b",
764 | data,
765 | expected: Ok(data.get("a/b").unwrap()), // 1
766 | },
767 | Test {
768 | ptr: "/c%d",
769 | data,
770 | expected: Ok(data.get("c%d").unwrap()), // 2
771 | },
772 | Test {
773 | ptr: "/e^f",
774 | data,
775 | expected: Ok(data.get("e^f").unwrap()), // 3
776 | },
777 | Test {
778 | ptr: "/g|h",
779 | data,
780 | expected: Ok(data.get("g|h").unwrap()), // 4
781 | },
782 | Test {
783 | ptr: "/i\\j",
784 | data,
785 | expected: Ok(data.get("i\\j").unwrap()), // 5
786 | },
787 | Test {
788 | ptr: "/k\"l",
789 | data,
790 | expected: Ok(data.get("k\"l").unwrap()), // 6
791 | },
792 | Test {
793 | ptr: "/ ",
794 | data,
795 | expected: Ok(data.get(" ").unwrap()), // 7
796 | },
797 | Test {
798 | ptr: "/m~0n",
799 | data,
800 | expected: Ok(data.get("m~n").unwrap()), // 8
801 | },
802 | Test {
803 | ptr: "/object/bool/unresolvable",
804 | data,
805 | expected: Err(Error::Unreachable {
806 | position: 2,
807 | offset: 12,
808 | }),
809 | },
810 | Test {
811 | ptr: "/object/not_found",
812 | data,
813 | expected: Err(Error::NotFound {
814 | position: 1,
815 | offset: 7,
816 | }),
817 | },
818 | ]);
819 | }
820 | struct Test<'v, V> {
821 | ptr: &'static str,
822 | expected: Result<&'v V, Error>,
823 | data: &'v V,
824 | }
825 |
826 | impl<'v, V> Test<'v, V>
827 | where
828 | V: Resolve
829 | + ResolveMut
830 | + Clone
831 | + PartialEq
832 | + fmt::Display
833 | + fmt::Debug,
834 | {
835 | fn all(tests: impl IntoIterator
- >) {
836 | tests.into_iter().enumerate().for_each(|(i, t)| t.run(i));
837 | }
838 |
839 | fn run(self, _i: usize) {
840 | _ = self;
841 | let Test {
842 | ptr,
843 | data,
844 | expected,
845 | } = self;
846 | let ptr = Pointer::from_static(ptr);
847 |
848 | // cloning the data & expected to make comparison easier
849 | let mut data = data.clone();
850 | let expected = expected.cloned();
851 |
852 | // testing Resolve
853 | let res = data.resolve(ptr).cloned();
854 | assert_eq!(&res, &expected);
855 |
856 | // testing ResolveMut
857 | let res = data.resolve_mut(ptr).cloned();
858 | assert_eq!(&res, &expected);
859 | }
860 | }
861 | }
862 |
--------------------------------------------------------------------------------