jsonptr/assign.rs
1//! # Assign values based on JSON [`Pointer`]s
2//!
3//! This module provides the [`Assign`] trait which allows for the assignment of
4//! values based on a JSON Pointer.
5//!
6//! This module is enabled by default with the `"assign"` feature flag.
7//!
8//! # Expansion
9//! The path will automatically be expanded if the [`Pointer`] is not fully
10//! exhausted before reaching a non-existent key in the case of objects, index
11//! in the case of arrays, or a scalar value (including `null`) based upon a
12//! best-guess effort on the meaning of each [`Token`](crate::Token):
13//! - If the [`Token`](crate::Token) is equal to `"0"` or `"-"`, the token will
14//! be considered an index of an array.
15//! - All tokens not equal to `"0"` or `"-"` will be considered keys of an
16//! object.
17//!
18//! ## Usage
19//! [`Assign`] can be used directly or through the [`assign`](Pointer::assign)
20//! method of [`Pointer`].
21//!
22//! ```rust
23//! use jsonptr::Pointer;
24//! use serde_json::json;
25//! let mut data = json!({"foo": "bar"});
26//! let ptr = Pointer::from_static("/foo");
27//! let replaced = ptr.assign(&mut data, "baz").unwrap();
28//! assert_eq!(replaced, Some(json!("bar")));
29//! assert_eq!(data, json!({"foo": "baz"}));
30//! ```
31//! ## Provided implementations
32//!
33//! | Lang | value type | feature flag | Default |
34//! | ----- |: ----------------- :|: ---------- :| ------- |
35//! | JSON | `serde_json::Value` | `"json"` | ✓ |
36//! | TOML | `toml::Value` | `"toml"` | |
37//!
38
39use crate::{
40 index::{OutOfBoundsError, ParseIndexError},
41 Pointer,
42};
43use core::fmt::{self, Debug};
44
45/*
46░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
47╔══════════════════════════════════════════════════════════════════════════════╗
48║ ║
49║ Assign ║
50║ ¯¯¯¯¯¯¯¯ ║
51╚══════════════════════════════════════════════════════════════════════════════╝
52░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
53*/
54
55/// Implemented by types which can internally assign a
56/// ([`Value`](`Assign::Value`)) at a path represented by a JSON [`Pointer`].
57///
58/// ## Expansion
59/// For provided implementations (`"json"`, and `"toml"`) path will
60/// automatically be expanded the if the [`Pointer`] is not fully exhausted
61/// before reaching a non-existent key in the case of objects, index in the case
62/// of arrays, or a scalar value (including `null`) based upon a best-guess
63/// effort on the meaning of each [`Token`](crate::Token):
64///
65/// - If the [`Token`](crate::Token) is equal to `"0"` or `"-"`, the token will
66/// be considered an index of an array.
67/// - All tokens not equal to `"0"` or `"-"` will be considered keys of an
68/// object.
69///
70/// ## Examples
71///
72/// ### Successful assignment with replacement
73/// This example demonstrates a successful assignment with replacement.
74/// ```rust
75/// use jsonptr::{Pointer, assign::Assign};
76/// use serde_json::{json, Value};
77///
78/// let mut data = json!({"foo": "bar"});
79/// let ptr = Pointer::from_static("/foo");
80///
81/// let replaced = data.assign(&ptr, "baz").unwrap();
82/// assert_eq!(replaced, Some(json!("bar")));
83/// assert_eq!(data, json!({"foo": "baz"}));
84/// ```
85///
86/// ### Successful assignment with path expansion
87/// This example demonstrates path expansion, including an array index (`"0"`)
88/// ```rust
89/// # use jsonptr::{Pointer, assign::Assign};
90/// # use serde_json::{json, Value};
91/// let ptr = Pointer::from_static("/foo/bar/0/baz");
92/// let mut data = serde_json::json!({"foo": "bar"});
93///
94/// let replaced = data.assign(ptr, json!("qux")).unwrap();
95///
96/// assert_eq!(&data, &json!({"foo": {"bar": [{"baz": "qux"}]}}));
97/// assert_eq!(replaced, Some(json!("bar")));
98/// ```
99///
100/// ### Successful assignment with `"-"` token
101///
102/// This example performs path expansion using the special `"-"` token (per RFC
103/// 6901) to represent the next element in an array.
104///
105/// ```rust
106/// # use jsonptr::{Pointer, assign::Assign};
107/// # use serde_json::{json, Value};
108/// let ptr = Pointer::from_static("/foo/bar/-/baz");
109/// let mut data = json!({"foo": "bar"});
110///
111/// let replaced = data.assign(ptr, json!("qux")).unwrap();
112/// assert_eq!(&data, &json!({"foo": {"bar": [{"baz": "qux"}]}}));
113/// assert_eq!(replaced, Some(json!("bar")));
114/// ```
115pub trait Assign {
116 /// The type of value that this implementation can operate on.
117 type Value;
118
119 /// Error associated with `Assign`
120 type Error;
121
122 /// Assigns a value of based on the path provided by a JSON Pointer,
123 /// returning the replaced value, if any.
124 ///
125 /// # Errors
126 /// Returns [`Self::Error`] if the assignment fails.
127 fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
128 where
129 V: Into<Self::Value>;
130}
131
132/*
133░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
134╔══════════════════════════════════════════════════════════════════════════════╗
135║ ║
136║ AssignError ║
137║ ¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
138╚══════════════════════════════════════════════════════════════════════════════╝
139░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
140*/
141
142/// Possible error returned from [`Assign`] implementations for
143/// [`serde_json::Value`] and
144/// [`toml::Value`](https://docs.rs/toml/0.8.14/toml/index.html).
145#[derive(Debug, PartialEq, Eq)]
146pub enum AssignError {
147 /// A `Token` within the `Pointer` failed to be parsed as an array index.
148 FailedToParseIndex {
149 /// Offset of the partial pointer starting with the invalid index.
150 offset: usize,
151 /// The source [`ParseIndexError`]
152 source: ParseIndexError,
153 },
154
155 /// target array.
156 OutOfBounds {
157 /// Offset of the partial pointer starting with the invalid index.
158 offset: usize,
159 /// The source [`OutOfBoundsError`]
160 source: OutOfBoundsError,
161 },
162}
163
164impl fmt::Display for AssignError {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 Self::FailedToParseIndex { offset, .. } => {
168 write!(
169 f,
170 "assignment failed due to an invalid index at offset {offset}"
171 )
172 }
173 Self::OutOfBounds { offset, .. } => {
174 write!(
175 f,
176 "assignment failed due to index at offset {offset} being out of bounds"
177 )
178 }
179 }
180 }
181}
182
183#[cfg(feature = "std")]
184impl std::error::Error for AssignError {
185 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
186 match self {
187 Self::FailedToParseIndex { source, .. } => Some(source),
188 Self::OutOfBounds { source, .. } => Some(source),
189 }
190 }
191}
192
193enum Assigned<'v, V> {
194 Done(Option<V>),
195 Continue { next_dest: &'v mut V, same_value: V },
196}
197
198/*
199░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
200╔══════════════════════════════════════════════════════════════════════════════╗
201║ ║
202║ json impl ║
203║ ¯¯¯¯¯¯¯¯¯¯¯ ║
204╚══════════════════════════════════════════════════════════════════════════════╝
205░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
206*/
207
208#[cfg(feature = "json")]
209mod json {
210 use super::{Assign, AssignError, Assigned};
211 use crate::{Pointer, Token};
212 use alloc::{
213 string::{String, ToString},
214 vec::Vec,
215 };
216
217 use core::mem;
218 use serde_json::{map::Entry, Map, Value};
219
220 fn expand(mut remaining: &Pointer, mut value: Value) -> Value {
221 while let Some((ptr, tok)) = remaining.split_back() {
222 remaining = ptr;
223 match tok.encoded() {
224 "0" | "-" => {
225 value = Value::Array(vec![value]);
226 }
227 _ => {
228 let mut obj = Map::new();
229 obj.insert(tok.to_string(), value);
230 value = Value::Object(obj);
231 }
232 }
233 }
234 value
235 }
236 impl Assign for Value {
237 type Value = Value;
238 type Error = AssignError;
239 fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
240 where
241 V: Into<Self::Value>,
242 {
243 assign_value(ptr, self, value.into())
244 }
245 }
246
247 pub(crate) fn assign_value(
248 mut ptr: &Pointer,
249 mut dest: &mut Value,
250 mut value: Value,
251 ) -> Result<Option<Value>, AssignError> {
252 let mut offset = 0;
253
254 while let Some((token, tail)) = ptr.split_front() {
255 let tok_len = token.encoded().len();
256
257 let assigned = match dest {
258 Value::Array(array) => assign_array(token, tail, array, value, offset)?,
259 Value::Object(obj) => assign_object(token, tail, obj, value),
260 _ => assign_scalar(ptr, dest, value),
261 };
262 match assigned {
263 Assigned::Done(assignment) => {
264 return Ok(assignment);
265 }
266 Assigned::Continue {
267 next_dest: next_value,
268 same_value: same_src,
269 } => {
270 value = same_src;
271 dest = next_value;
272 ptr = tail;
273 }
274 }
275 offset += 1 + tok_len;
276 }
277
278 // Pointer is root, we can replace `dest` directly
279 let replaced = Some(core::mem::replace(dest, value));
280 Ok(replaced)
281 }
282 #[allow(clippy::needless_pass_by_value)]
283 fn assign_array<'v>(
284 token: Token<'_>,
285 remaining: &Pointer,
286 array: &'v mut Vec<Value>,
287 src: Value,
288 offset: usize,
289 ) -> Result<Assigned<'v, Value>, AssignError> {
290 // parsing the index
291 let idx = token
292 .to_index()
293 .map_err(|source| AssignError::FailedToParseIndex { offset, source })?
294 .for_len_incl(array.len())
295 .map_err(|source| AssignError::OutOfBounds { offset, source })?;
296
297 debug_assert!(idx <= array.len());
298
299 if idx < array.len() {
300 // element exists in the array, we either need to replace it or continue
301 // depending on whether this is the last token or not
302 if remaining.is_root() {
303 // last token, we replace the value and call it a day
304 Ok(Assigned::Done(Some(mem::replace(&mut array[idx], src))))
305 } else {
306 // not the last token, we continue with a mut ref to the element as
307 // the next value
308 Ok(Assigned::Continue {
309 next_dest: &mut array[idx],
310 same_value: src,
311 })
312 }
313 } else {
314 // element does not exist in the array.
315 // we create the path and assign the value
316 let src = expand(remaining, src);
317 array.push(src);
318 Ok(Assigned::Done(None))
319 }
320 }
321
322 #[allow(clippy::needless_pass_by_value)]
323 fn assign_object<'v>(
324 token: Token<'_>,
325 remaining: &Pointer,
326 obj: &'v mut Map<String, Value>,
327 src: Value,
328 ) -> Assigned<'v, Value> {
329 // grabbing the entry of the token
330 let entry = obj.entry(token.to_string());
331 // adding token to the pointer buf
332
333 match entry {
334 Entry::Occupied(entry) => {
335 // if the entry exists, we either replace it or continue
336 let entry = entry.into_mut();
337 if remaining.is_root() {
338 // if this is the last token, we are done
339 // grab the old value and replace it with the new one
340 Assigned::Done(Some(mem::replace(entry, src)))
341 } else {
342 // if this is not the last token, we continue with a mutable
343 // reference to the entry as the next value
344 Assigned::Continue {
345 same_value: src,
346 next_dest: entry,
347 }
348 }
349 }
350 Entry::Vacant(entry) => {
351 // if the entry does not exist, we create a value based on the
352 // remaining path with the src value as a leaf and assign it to the
353 // entry
354 entry.insert(expand(remaining, src));
355 Assigned::Done(None)
356 }
357 }
358 }
359
360 fn assign_scalar<'v>(
361 remaining: &Pointer,
362 scalar: &'v mut Value,
363 value: Value,
364 ) -> Assigned<'v, Value> {
365 // scalar values are always replaced at the current buf (with its token)
366 // build the new src and we replace the value with it.
367 let replaced = Some(mem::replace(scalar, expand(remaining, value)));
368 Assigned::Done(replaced)
369 }
370}
371
372/*
373░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
374╔══════════════════════════════════════════════════════════════════════════════╗
375║ ║
376║ toml impl ║
377║ ¯¯¯¯¯¯¯¯¯¯¯ ║
378╚══════════════════════════════════════════════════════════════════════════════╝
379░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
380*/
381
382#[cfg(feature = "toml")]
383mod toml {
384 use super::{Assign, AssignError, Assigned};
385 use crate::{Pointer, Token};
386 use alloc::{string::String, vec, vec::Vec};
387 use core::mem;
388 use toml::{map::Entry, map::Map, Value};
389
390 fn expand(mut remaining: &Pointer, mut value: Value) -> Value {
391 while let Some((ptr, tok)) = remaining.split_back() {
392 remaining = ptr;
393 match tok.encoded() {
394 "0" | "-" => {
395 value = Value::Array(vec![value]);
396 }
397 _ => {
398 let mut obj = Map::new();
399 obj.insert(tok.to_string(), value);
400 value = Value::Table(obj);
401 }
402 }
403 }
404 value
405 }
406
407 impl Assign for Value {
408 type Value = Value;
409 type Error = AssignError;
410 fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
411 where
412 V: Into<Self::Value>,
413 {
414 assign_value(ptr, self, value.into())
415 }
416 }
417
418 pub(crate) fn assign_value(
419 mut ptr: &Pointer,
420 mut dest: &mut Value,
421 mut value: Value,
422 ) -> Result<Option<Value>, AssignError> {
423 let mut offset = 0;
424
425 while let Some((token, tail)) = ptr.split_front() {
426 let tok_len = token.encoded().len();
427
428 let assigned = match dest {
429 Value::Array(array) => assign_array(token, tail, array, value, offset)?,
430 Value::Table(tbl) => assign_object(token, tail, tbl, value),
431 _ => assign_scalar(ptr, dest, value),
432 };
433 match assigned {
434 Assigned::Done(assignment) => {
435 return Ok(assignment);
436 }
437 Assigned::Continue {
438 next_dest: next_value,
439 same_value: same_src,
440 } => {
441 value = same_src;
442 dest = next_value;
443 ptr = tail;
444 }
445 }
446 offset += 1 + tok_len;
447 }
448
449 // Pointer is root, we can replace `dest` directly
450 let replaced = Some(mem::replace(dest, value));
451 Ok(replaced)
452 }
453
454 #[allow(clippy::needless_pass_by_value)]
455 fn assign_array<'v>(
456 token: Token<'_>,
457 remaining: &Pointer,
458 array: &'v mut Vec<Value>,
459 src: Value,
460 offset: usize,
461 ) -> Result<Assigned<'v, Value>, AssignError> {
462 // parsing the index
463 let idx = token
464 .to_index()
465 .map_err(|source| AssignError::FailedToParseIndex { offset, source })?
466 .for_len_incl(array.len())
467 .map_err(|source| AssignError::OutOfBounds { offset, source })?;
468
469 debug_assert!(idx <= array.len());
470
471 if idx < array.len() {
472 // element exists in the array, we either need to replace it or continue
473 // depending on whether this is the last token or not
474 if remaining.is_root() {
475 // last token, we replace the value and call it a day
476 Ok(Assigned::Done(Some(mem::replace(&mut array[idx], src))))
477 } else {
478 // not the last token, we continue with a mut ref to the element as
479 // the next value
480 Ok(Assigned::Continue {
481 next_dest: &mut array[idx],
482 same_value: src,
483 })
484 }
485 } else {
486 // element does not exist in the array.
487 // we create the path and assign the value
488 let src = expand(remaining, src);
489 array.push(src);
490 Ok(Assigned::Done(None))
491 }
492 }
493
494 #[allow(clippy::needless_pass_by_value)]
495 fn assign_object<'v>(
496 token: Token<'_>,
497 remaining: &Pointer,
498 obj: &'v mut Map<String, Value>,
499 src: Value,
500 ) -> Assigned<'v, Value> {
501 // grabbing the entry of the token
502 match obj.entry(token.to_string()) {
503 Entry::Occupied(entry) => {
504 // if the entry exists, we either replace it or continue
505 let entry = entry.into_mut();
506 if remaining.is_root() {
507 // if this is the last token, we are done
508 // grab the old value and replace it with the new one
509 Assigned::Done(Some(mem::replace(entry, src)))
510 } else {
511 // if this is not the last token, we continue with a mutable
512 // reference to the entry as the next value
513 Assigned::Continue {
514 same_value: src,
515 next_dest: entry,
516 }
517 }
518 }
519 Entry::Vacant(entry) => {
520 // if the entry does not exist, we create a value based on the
521 // remaining path with the src value as a leaf and assign it to the
522 // entry
523 entry.insert(expand(remaining, src));
524 Assigned::Done(None)
525 }
526 }
527 }
528
529 fn assign_scalar<'v>(
530 remaining: &Pointer,
531 scalar: &'v mut Value,
532 value: Value,
533 ) -> Assigned<'v, Value> {
534 // scalar values are always replaced at the current buf (with its token)
535 // build the new src and we replace the value with it.
536 Assigned::Done(Some(mem::replace(scalar, expand(remaining, value))))
537 }
538}
539
540/*
541░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
542╔══════════════════════════════════════════════════════════════════════════════╗
543║ ║
544║ Tests ║
545║ ¯¯¯¯¯¯¯ ║
546╚══════════════════════════════════════════════════════════════════════════════╝
547░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
548*/
549
550#[cfg(test)]
551#[allow(clippy::too_many_lines)]
552mod tests {
553 use super::{Assign, AssignError};
554 use crate::{
555 index::{OutOfBoundsError, ParseIndexError},
556 Pointer,
557 };
558 use alloc::str::FromStr;
559 use core::fmt::{Debug, Display};
560
561 #[derive(Debug)]
562 struct Test<V: Assign> {
563 data: V,
564 ptr: &'static str,
565 assign: V,
566 expected_data: V,
567 expected: Result<Option<V>, V::Error>,
568 }
569
570 impl<V> Test<V>
571 where
572 V: Assign + Clone + PartialEq + Display + Debug,
573 V::Value: Debug + PartialEq + From<V>,
574 V::Error: Debug + PartialEq,
575 Result<Option<V>, V::Error>: PartialEq<Result<Option<V::Value>, V::Error>>,
576 {
577 fn all(tests: impl IntoIterator<Item = Test<V>>) {
578 tests.into_iter().enumerate().for_each(|(i, t)| t.run(i));
579 }
580 fn run(self, i: usize) {
581 let Test {
582 ptr,
583 mut data,
584 assign,
585 expected_data,
586 expected,
587 ..
588 } = self;
589 let ptr = Pointer::from_static(ptr);
590 let replaced = ptr.assign(&mut data, assign.clone());
591 assert_eq!(
592 &expected_data, &data,
593 "test #{i}:\n\ndata: \n{data:#?}\n\nexpected_data\n{expected_data:#?}"
594 );
595 assert_eq!(&expected, &replaced);
596 }
597 }
598
599 /*
600 ╔═══════════════════════════════════════════════════╗
601 ║ json ║
602 ╚═══════════════════════════════════════════════════╝
603 */
604
605 #[test]
606 #[cfg(feature = "json")]
607 fn assign_json() {
608 use alloc::vec;
609 use serde_json::json;
610 Test::all([
611 Test {
612 ptr: "/foo",
613 data: json!({}),
614 assign: json!("bar"),
615 expected_data: json!({"foo": "bar"}),
616 expected: Ok(None),
617 },
618 Test {
619 ptr: "",
620 data: json!({"foo": "bar"}),
621 assign: json!("baz"),
622 expected_data: json!("baz"),
623 expected: Ok(Some(json!({"foo": "bar"}))),
624 },
625 Test {
626 ptr: "/foo",
627 data: json!({"foo": "bar"}),
628 assign: json!("baz"),
629 expected_data: json!({"foo": "baz"}),
630 expected: Ok(Some(json!("bar"))),
631 },
632 Test {
633 ptr: "/foo/bar",
634 data: json!({"foo": "bar"}),
635 assign: json!("baz"),
636 expected_data: json!({"foo": {"bar": "baz"}}),
637 expected: Ok(Some(json!("bar"))),
638 },
639 Test {
640 ptr: "/foo/bar",
641 data: json!({}),
642 assign: json!("baz"),
643 expected_data: json!({"foo": {"bar": "baz"}}),
644 expected: Ok(None),
645 },
646 Test {
647 ptr: "/",
648 data: json!({}),
649 assign: json!("foo"),
650 expected_data: json!({"": "foo"}),
651 expected: Ok(None),
652 },
653 Test {
654 ptr: "/-",
655 data: json!({}),
656 assign: json!("foo"),
657 expected_data: json!({"-": "foo"}),
658 expected: Ok(None),
659 },
660 Test {
661 ptr: "/-",
662 data: json!(null),
663 assign: json!(34),
664 expected_data: json!([34]),
665 expected: Ok(Some(json!(null))),
666 },
667 Test {
668 ptr: "/foo/-",
669 data: json!({"foo": "bar"}),
670 assign: json!("baz"),
671 expected_data: json!({"foo": ["baz"]}),
672 expected: Ok(Some(json!("bar"))),
673 },
674 Test {
675 ptr: "/foo/-/bar",
676 assign: "baz".into(),
677 data: json!({}),
678 expected: Ok(None),
679 expected_data: json!({"foo":[{"bar": "baz"}]}),
680 },
681 Test {
682 ptr: "/foo/-/bar",
683 assign: "qux".into(),
684 data: json!({"foo":[{"bar":"baz" }]}),
685 expected: Ok(None),
686 expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}),
687 },
688 Test {
689 ptr: "/foo/-/bar",
690 data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}),
691 assign: "quux".into(),
692 expected: Ok(None),
693 expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}),
694 },
695 Test {
696 ptr: "/foo/0/bar",
697 data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}),
698 assign: "grault".into(),
699 expected: Ok(Some("baz".into())),
700 expected_data: json!({"foo":[{"bar":"grault"},{"bar":"qux"},{"bar":"quux"}]}),
701 },
702 Test {
703 ptr: "/0",
704 data: json!({}),
705 assign: json!("foo"),
706 expected_data: json!({"0": "foo"}),
707 expected: Ok(None),
708 },
709 Test {
710 ptr: "/1",
711 data: json!(null),
712 assign: json!("foo"),
713 expected_data: json!({"1": "foo"}),
714 expected: Ok(Some(json!(null))),
715 },
716 Test {
717 ptr: "/0",
718 data: json!([]),
719 expected_data: json!(["foo"]),
720 assign: json!("foo"),
721 expected: Ok(None),
722 },
723 Test {
724 ptr: "///bar",
725 data: json!({"":{"":{"bar": 42}}}),
726 assign: json!(34),
727 expected_data: json!({"":{"":{"bar":34}}}),
728 expected: Ok(Some(json!(42))),
729 },
730 Test {
731 ptr: "/1",
732 data: json!([]),
733 assign: json!("foo"),
734 expected: Err(AssignError::OutOfBounds {
735 offset: 0,
736 source: OutOfBoundsError {
737 index: 1,
738 length: 0,
739 },
740 }),
741 expected_data: json!([]),
742 },
743 Test {
744 ptr: "/0",
745 data: json!(["foo"]),
746 assign: json!("bar"),
747 expected: Ok(Some(json!("foo"))),
748 expected_data: json!(["bar"]),
749 },
750 Test {
751 ptr: "/a",
752 data: json!([]),
753 assign: json!("foo"),
754 expected: Err(AssignError::FailedToParseIndex {
755 offset: 0,
756 source: ParseIndexError::InvalidInteger(usize::from_str("foo").unwrap_err()),
757 }),
758 expected_data: json!([]),
759 },
760 Test {
761 ptr: "/002",
762 data: json!([]),
763 assign: json!("foo"),
764 expected: Err(AssignError::FailedToParseIndex {
765 offset: 0,
766 source: ParseIndexError::LeadingZeros,
767 }),
768 expected_data: json!([]),
769 },
770 ]);
771 }
772
773 /*
774 ╔══════════════════════════════════════════════════╗
775 ║ toml ║
776 ╚══════════════════════════════════════════════════╝
777 */
778
779 #[test]
780 #[cfg(feature = "toml")]
781 fn assign_toml() {
782 use alloc::vec;
783 use toml::{toml, Table, Value};
784 Test::all([
785 Test {
786 data: Value::Table(toml::Table::new()),
787 ptr: "/foo",
788 assign: "bar".into(),
789 expected_data: toml! { "foo" = "bar" }.into(),
790 expected: Ok(None),
791 },
792 Test {
793 data: toml! {foo = "bar"}.into(),
794 ptr: "",
795 assign: "baz".into(),
796 expected_data: "baz".into(),
797 expected: Ok(Some(toml! {foo = "bar"}.into())),
798 },
799 Test {
800 data: toml! { foo = "bar"}.into(),
801 ptr: "/foo",
802 assign: "baz".into(),
803 expected_data: toml! {foo = "baz"}.into(),
804 expected: Ok(Some("bar".into())),
805 },
806 Test {
807 data: toml! { foo = "bar"}.into(),
808 ptr: "/foo/bar",
809 assign: "baz".into(),
810 expected_data: toml! {foo = { bar = "baz"}}.into(),
811 expected: Ok(Some("bar".into())),
812 },
813 Test {
814 data: Table::new().into(),
815 ptr: "/",
816 assign: "foo".into(),
817 expected_data: toml! {"" = "foo"}.into(),
818 expected: Ok(None),
819 },
820 Test {
821 data: Table::new().into(),
822 ptr: "/-",
823 assign: "foo".into(),
824 expected_data: toml! {"-" = "foo"}.into(),
825 expected: Ok(None),
826 },
827 Test {
828 data: "data".into(),
829 ptr: "/-",
830 assign: 34.into(),
831 expected_data: Value::Array(vec![34.into()]),
832 expected: Ok(Some("data".into())),
833 },
834 Test {
835 data: toml! {foo = "bar"}.into(),
836 ptr: "/foo/-",
837 assign: "baz".into(),
838 expected_data: toml! {foo = ["baz"]}.into(),
839 expected: Ok(Some("bar".into())),
840 },
841 Test {
842 data: Table::new().into(),
843 ptr: "/0",
844 assign: "foo".into(),
845 expected_data: toml! {"0" = "foo"}.into(),
846 expected: Ok(None),
847 },
848 Test {
849 data: 21.into(),
850 ptr: "/1",
851 assign: "foo".into(),
852 expected_data: toml! {"1" = "foo"}.into(),
853 expected: Ok(Some(21.into())),
854 },
855 Test {
856 data: Value::Array(vec![]),
857 ptr: "/0",
858 expected_data: vec![Value::from("foo")].into(),
859 assign: "foo".into(),
860 expected: Ok(None),
861 },
862 Test {
863 ptr: "/foo/-/bar",
864 assign: "baz".into(),
865 data: Table::new().into(),
866 expected: Ok(None),
867 expected_data: toml! { "foo" = [{"bar" = "baz"}] }.into(),
868 },
869 Test {
870 ptr: "/foo/-/bar",
871 assign: "qux".into(),
872 data: toml! {"foo" = [{"bar" = "baz"}] }.into(),
873 expected: Ok(None),
874 expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(),
875 },
876 Test {
877 ptr: "/foo/-/bar",
878 data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(),
879 assign: "quux".into(),
880 expected: Ok(None),
881 expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]}
882 .into(),
883 },
884 Test {
885 ptr: "/foo/0/bar",
886 data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(),
887 assign: "grault".into(),
888 expected: Ok(Some("baz".into())),
889 expected_data:
890 toml! {"foo" = [{"bar" = "grault"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(),
891 },
892 Test {
893 data: Value::Array(vec![]),
894 ptr: "/-",
895 assign: "foo".into(),
896 expected: Ok(None),
897 expected_data: vec!["foo"].into(),
898 },
899 Test {
900 data: Value::Array(vec![]),
901 ptr: "/1",
902 assign: "foo".into(),
903 expected: Err(AssignError::OutOfBounds {
904 offset: 0,
905 source: OutOfBoundsError {
906 index: 1,
907 length: 0,
908 },
909 }),
910 expected_data: Value::Array(vec![]),
911 },
912 Test {
913 data: Value::Array(vec![]),
914 ptr: "/a",
915 assign: "foo".into(),
916 expected: Err(AssignError::FailedToParseIndex {
917 offset: 0,
918 source: ParseIndexError::InvalidInteger(usize::from_str("foo").unwrap_err()),
919 }),
920 expected_data: Value::Array(vec![]),
921 },
922 ]);
923 }
924}