1use crate::{
40 diagnostic::{diagnostic_url, Diagnostic, Label},
41 index::{OutOfBoundsError, ParseIndexError},
42 Pointer, PointerBuf,
43};
44use alloc::{boxed::Box, string::ToString};
45use core::{
46 fmt::{self, Debug},
47 iter::once,
48};
49
50pub trait Assign {
111 type Value;
113
114 type Error;
116
117 fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
123 where
124 V: Into<Self::Value>;
125}
126
127#[deprecated(since = "0.7.0", note = "renamed to `Error`")]
129pub type AssignError = Error;
130
131#[derive(Debug, PartialEq, Eq)]
135pub enum Error {
136 FailedToParseIndex {
139 position: usize,
141 offset: usize,
143 source: ParseIndexError,
145 },
146
147 OutOfBounds {
152 position: usize,
154 offset: usize,
156 source: OutOfBoundsError,
158 },
159}
160
161impl Error {
162 pub fn position(&self) -> usize {
164 match self {
165 Self::OutOfBounds { position, .. } | Self::FailedToParseIndex { position, .. } => {
166 *position
167 }
168 }
169 }
170 pub fn offset(&self) -> usize {
172 match self {
173 Self::OutOfBounds { offset, .. } | Self::FailedToParseIndex { offset, .. } => *offset,
174 }
175 }
176
177 #[must_use]
181 pub fn is_out_of_bounds(&self) -> bool {
182 matches!(self, Self::OutOfBounds { .. })
183 }
184
185 #[must_use]
189 pub fn is_failed_to_parse_index(&self) -> bool {
190 matches!(self, Self::FailedToParseIndex { .. })
191 }
192}
193
194impl fmt::Display for Error {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 match self {
197 Self::FailedToParseIndex { offset, .. } => {
198 write!(
199 f,
200 "assign failed: json pointer token at offset {offset} failed to parse as an array index"
201 )
202 }
203 Self::OutOfBounds { offset, .. } => write!(
204 f,
205 "assign failed: json pointer token at offset {offset} is out of bounds",
206 ),
207 }
208 }
209}
210
211impl Diagnostic for Error {
212 type Subject = PointerBuf;
213
214 fn url() -> &'static str {
215 diagnostic_url!(enum assign::Error)
216 }
217
218 fn labels(&self, origin: &Self::Subject) -> Option<Box<dyn Iterator<Item = Label>>> {
219 let position = self.position();
220 let token = origin.get(position)?;
221 let offset = if self.offset() + 1 < origin.as_str().len() {
222 self.offset() + 1
223 } else {
224 self.offset()
225 };
226 let len = token.encoded().len();
227 let text = match self {
228 Error::FailedToParseIndex { .. } => "expected array index or '-'".to_string(),
229 Error::OutOfBounds { source, .. } => {
230 format!("{} is out of bounds (len: {})", source.index, source.length)
231 }
232 };
233 Some(Box::new(once(Label::new(text, offset, len))))
234 }
235}
236
237#[cfg(feature = "miette")]
238impl miette::Diagnostic for Error {
239 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
240 Some(Box::new(<Self as Diagnostic>::url()))
241 }
242}
243
244#[cfg(feature = "std")]
245impl std::error::Error for Error {
246 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
247 match self {
248 Self::FailedToParseIndex { source, .. } => Some(source),
249 Self::OutOfBounds { source, .. } => Some(source),
250 }
251 }
252}
253
254#[cfg(feature = "json")]
255mod json {
256 use super::{Assign, Assigned, Error};
257 use crate::{Pointer, Token};
258 use alloc::{
259 string::{String, ToString},
260 vec::Vec,
261 };
262
263 use core::mem;
264 use serde_json::{map::Entry, Map, Value};
265
266 fn expand(mut remaining: &Pointer, mut value: Value) -> Value {
267 while let Some((ptr, tok)) = remaining.split_back() {
268 remaining = ptr;
269 match tok.encoded() {
270 "0" | "-" => {
271 value = Value::Array(vec![value]);
272 }
273 _ => {
274 let mut obj = Map::new();
275 obj.insert(tok.to_string(), value);
276 value = Value::Object(obj);
277 }
278 }
279 }
280 value
281 }
282 impl Assign for Value {
283 type Value = Value;
284 type Error = Error;
285 fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
286 where
287 V: Into<Self::Value>,
288 {
289 assign_value(ptr, self, value.into())
290 }
291 }
292
293 pub(crate) fn assign_value(
294 mut ptr: &Pointer,
295 mut dest: &mut Value,
296 mut value: Value,
297 ) -> Result<Option<Value>, Error> {
298 let mut offset = 0;
299
300 let mut position = 0;
301 while let Some((token, tail)) = ptr.split_front() {
302 let tok_len = token.encoded().len();
303
304 let assigned = match dest {
305 Value::Array(array) => assign_array(token, tail, array, value, position, offset)?,
306 Value::Object(obj) => assign_object(token, tail, obj, value),
307 _ => assign_scalar(ptr, dest, value),
308 };
309 match assigned {
310 Assigned::Done(assignment) => {
311 return Ok(assignment);
312 }
313 Assigned::Continue {
314 next_dest: next_value,
315 same_value: same_src,
316 } => {
317 value = same_src;
318 dest = next_value;
319 ptr = tail;
320 }
321 }
322 offset += 1 + tok_len;
323 position += 1;
324 }
325
326 let replaced = Some(core::mem::replace(dest, value));
328 Ok(replaced)
329 }
330 #[allow(clippy::needless_pass_by_value)]
331 fn assign_array<'v>(
332 token: Token<'_>,
333 remaining: &Pointer,
334 array: &'v mut Vec<Value>,
335 src: Value,
336 position: usize,
337 offset: usize,
338 ) -> Result<Assigned<'v, Value>, Error> {
339 let idx = token
341 .to_index()
342 .map_err(|source| Error::FailedToParseIndex {
343 position,
344 offset,
345 source,
346 })?
347 .for_len_incl(array.len())
348 .map_err(|source| Error::OutOfBounds {
349 position,
350 offset,
351 source,
352 })?;
353
354 debug_assert!(idx <= array.len());
355
356 if idx < array.len() {
357 if remaining.is_root() {
360 Ok(Assigned::Done(Some(mem::replace(&mut array[idx], src))))
362 } else {
363 Ok(Assigned::Continue {
366 next_dest: &mut array[idx],
367 same_value: src,
368 })
369 }
370 } else {
371 let src = expand(remaining, src);
374 array.push(src);
375 Ok(Assigned::Done(None))
376 }
377 }
378
379 #[allow(clippy::needless_pass_by_value)]
380 fn assign_object<'v>(
381 token: Token<'_>,
382 remaining: &Pointer,
383 obj: &'v mut Map<String, Value>,
384 src: Value,
385 ) -> Assigned<'v, Value> {
386 let entry = obj.entry(token.to_string());
388 match entry {
391 Entry::Occupied(entry) => {
392 let entry = entry.into_mut();
394 if remaining.is_root() {
395 Assigned::Done(Some(mem::replace(entry, src)))
398 } else {
399 Assigned::Continue {
402 same_value: src,
403 next_dest: entry,
404 }
405 }
406 }
407 Entry::Vacant(entry) => {
408 entry.insert(expand(remaining, src));
412 Assigned::Done(None)
413 }
414 }
415 }
416
417 fn assign_scalar<'v>(
418 remaining: &Pointer,
419 scalar: &'v mut Value,
420 value: Value,
421 ) -> Assigned<'v, Value> {
422 let replaced = Some(mem::replace(scalar, expand(remaining, value)));
425 Assigned::Done(replaced)
426 }
427}
428
429#[cfg(feature = "toml")]
430mod toml {
431 use super::{Assign, Assigned, Error};
432 use crate::{Pointer, Token};
433 use alloc::{string::String, vec, vec::Vec};
434 use core::mem;
435 use toml::{map::Entry, map::Map, Value};
436
437 fn expand(mut remaining: &Pointer, mut value: Value) -> Value {
438 while let Some((ptr, tok)) = remaining.split_back() {
439 remaining = ptr;
440 match tok.encoded() {
441 "0" | "-" => {
442 value = Value::Array(vec![value]);
443 }
444 _ => {
445 let mut obj = Map::new();
446 obj.insert(tok.to_string(), value);
447 value = Value::Table(obj);
448 }
449 }
450 }
451 value
452 }
453
454 impl Assign for Value {
455 type Value = Value;
456 type Error = Error;
457 fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
458 where
459 V: Into<Self::Value>,
460 {
461 assign_value(ptr, self, value.into())
462 }
463 }
464
465 pub(crate) fn assign_value(
466 mut ptr: &Pointer,
467 mut dest: &mut Value,
468 mut value: Value,
469 ) -> Result<Option<Value>, Error> {
470 let mut offset = 0;
471 let mut position = 0;
472
473 while let Some((token, tail)) = ptr.split_front() {
474 let tok_len = token.encoded().len();
475
476 let assigned = match dest {
477 Value::Array(array) => assign_array(token, tail, array, value, position, offset)?,
478 Value::Table(tbl) => assign_object(token, tail, tbl, value),
479 _ => assign_scalar(ptr, dest, value),
480 };
481 match assigned {
482 Assigned::Done(assignment) => {
483 return Ok(assignment);
484 }
485 Assigned::Continue {
486 next_dest: next_value,
487 same_value: same_src,
488 } => {
489 value = same_src;
490 dest = next_value;
491 ptr = tail;
492 }
493 }
494 offset += 1 + tok_len;
495 position += 1;
496 }
497
498 let replaced = Some(mem::replace(dest, value));
500 Ok(replaced)
501 }
502
503 #[allow(clippy::needless_pass_by_value)]
504 fn assign_array<'v>(
505 token: Token<'_>,
506 remaining: &Pointer,
507 array: &'v mut Vec<Value>,
508 src: Value,
509 position: usize,
510 offset: usize,
511 ) -> Result<Assigned<'v, Value>, Error> {
512 let idx = token
514 .to_index()
515 .map_err(|source| Error::FailedToParseIndex {
516 position,
517 offset,
518 source,
519 })?
520 .for_len_incl(array.len())
521 .map_err(|source| Error::OutOfBounds {
522 position,
523 offset,
524 source,
525 })?;
526
527 debug_assert!(idx <= array.len());
528
529 if idx < array.len() {
530 if remaining.is_root() {
533 Ok(Assigned::Done(Some(mem::replace(&mut array[idx], src))))
535 } else {
536 Ok(Assigned::Continue {
539 next_dest: &mut array[idx],
540 same_value: src,
541 })
542 }
543 } else {
544 let src = expand(remaining, src);
547 array.push(src);
548 Ok(Assigned::Done(None))
549 }
550 }
551
552 #[allow(clippy::needless_pass_by_value)]
553 fn assign_object<'v>(
554 token: Token<'_>,
555 remaining: &Pointer,
556 obj: &'v mut Map<String, Value>,
557 src: Value,
558 ) -> Assigned<'v, Value> {
559 match obj.entry(token.to_string()) {
561 Entry::Occupied(entry) => {
562 let entry = entry.into_mut();
564 if remaining.is_root() {
565 Assigned::Done(Some(mem::replace(entry, src)))
568 } else {
569 Assigned::Continue {
572 same_value: src,
573 next_dest: entry,
574 }
575 }
576 }
577 Entry::Vacant(entry) => {
578 entry.insert(expand(remaining, src));
582 Assigned::Done(None)
583 }
584 }
585 }
586
587 fn assign_scalar<'v>(
588 remaining: &Pointer,
589 scalar: &'v mut Value,
590 value: Value,
591 ) -> Assigned<'v, Value> {
592 Assigned::Done(Some(mem::replace(scalar, expand(remaining, value))))
595 }
596}
597
598enum Assigned<'v, V> {
599 Done(Option<V>),
600 Continue { next_dest: &'v mut V, same_value: V },
601}
602
603#[cfg(test)]
604#[allow(clippy::too_many_lines)]
605mod tests {
606 use super::{Assign, Error};
607 use crate::{
608 index::{InvalidCharacterError, OutOfBoundsError, ParseIndexError},
609 Pointer,
610 };
611 use alloc::vec;
612 use core::fmt::{Debug, Display};
613
614 #[derive(Debug)]
615 struct Test<V: Assign> {
616 data: V,
617 ptr: &'static str,
618 assign: V,
619 expected_data: V,
620 expected: Result<Option<V>, V::Error>,
621 }
622
623 impl<V> Test<V>
624 where
625 V: Assign + Clone + PartialEq + Display + Debug,
626 V::Value: Debug + PartialEq + From<V>,
627 V::Error: Debug + PartialEq,
628 Result<Option<V>, V::Error>: PartialEq<Result<Option<V::Value>, V::Error>>,
629 {
630 fn run(self, i: usize) {
631 let Test {
632 ptr,
633 mut data,
634 assign,
635 expected_data,
636 expected,
637 ..
638 } = self;
639 let ptr = Pointer::from_static(ptr);
640 let replaced = ptr.assign(&mut data, assign.clone());
641 assert_eq!(
642 &expected_data, &data,
643 "test #{i}:\n\ndata: \n{data:#?}\n\nexpected_data\n{expected_data:#?}"
644 );
645 assert_eq!(&expected, &replaced);
646 }
647 }
648
649 #[test]
650 #[cfg(feature = "json")]
651 fn assign_json() {
652 use serde_json::json;
653 [
654 Test {
655 ptr: "/foo",
656 data: json!({}),
657 assign: json!("bar"),
658 expected_data: json!({"foo": "bar"}),
659 expected: Ok(None),
660 },
661 Test {
662 ptr: "",
663 data: json!({"foo": "bar"}),
664 assign: json!("baz"),
665 expected_data: json!("baz"),
666 expected: Ok(Some(json!({"foo": "bar"}))),
667 },
668 Test {
669 ptr: "/foo",
670 data: json!({"foo": "bar"}),
671 assign: json!("baz"),
672 expected_data: json!({"foo": "baz"}),
673 expected: Ok(Some(json!("bar"))),
674 },
675 Test {
676 ptr: "/foo/bar",
677 data: json!({"foo": "bar"}),
678 assign: json!("baz"),
679 expected_data: json!({"foo": {"bar": "baz"}}),
680 expected: Ok(Some(json!("bar"))),
681 },
682 Test {
683 ptr: "/foo/bar",
684 data: json!({}),
685 assign: json!("baz"),
686 expected_data: json!({"foo": {"bar": "baz"}}),
687 expected: Ok(None),
688 },
689 Test {
690 ptr: "/",
691 data: json!({}),
692 assign: json!("foo"),
693 expected_data: json!({"": "foo"}),
694 expected: Ok(None),
695 },
696 Test {
697 ptr: "/-",
698 data: json!({}),
699 assign: json!("foo"),
700 expected_data: json!({"-": "foo"}),
701 expected: Ok(None),
702 },
703 Test {
704 ptr: "/-",
705 data: json!(null),
706 assign: json!(34),
707 expected_data: json!([34]),
708 expected: Ok(Some(json!(null))),
709 },
710 Test {
711 ptr: "/foo/-",
712 data: json!({"foo": "bar"}),
713 assign: json!("baz"),
714 expected_data: json!({"foo": ["baz"]}),
715 expected: Ok(Some(json!("bar"))),
716 },
717 Test {
718 ptr: "/foo/-/bar",
719 assign: "baz".into(),
720 data: json!({}),
721 expected: Ok(None),
722 expected_data: json!({"foo":[{"bar": "baz"}]}),
723 },
724 Test {
725 ptr: "/foo/-/bar",
726 assign: "qux".into(),
727 data: json!({"foo":[{"bar":"baz" }]}),
728 expected: Ok(None),
729 expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}),
730 },
731 Test {
732 ptr: "/foo/-/bar",
733 data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}),
734 assign: "quux".into(),
735 expected: Ok(None),
736 expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}),
737 },
738 Test {
739 ptr: "/foo/0/bar",
740 data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}),
741 assign: "grault".into(),
742 expected: Ok(Some("baz".into())),
743 expected_data: json!({"foo":[{"bar":"grault"},{"bar":"qux"},{"bar":"quux"}]}),
744 },
745 Test {
746 ptr: "/0",
747 data: json!({}),
748 assign: json!("foo"),
749 expected_data: json!({"0": "foo"}),
750 expected: Ok(None),
751 },
752 Test {
753 ptr: "/1",
754 data: json!(null),
755 assign: json!("foo"),
756 expected_data: json!({"1": "foo"}),
757 expected: Ok(Some(json!(null))),
758 },
759 Test {
760 ptr: "/0",
761 data: json!([]),
762 expected_data: json!(["foo"]),
763 assign: json!("foo"),
764 expected: Ok(None),
765 },
766 Test {
767 ptr: "///bar",
768 data: json!({"":{"":{"bar": 42}}}),
769 assign: json!(34),
770 expected_data: json!({"":{"":{"bar":34}}}),
771 expected: Ok(Some(json!(42))),
772 },
773 Test {
774 ptr: "/1",
775 data: json!([]),
776 assign: json!("foo"),
777 expected: Err(Error::OutOfBounds {
778 position: 0,
779 offset: 0,
780 source: OutOfBoundsError {
781 index: 1,
782 length: 0,
783 },
784 }),
785 expected_data: json!([]),
786 },
787 Test {
788 ptr: "/0",
789 data: json!(["foo"]),
790 assign: json!("bar"),
791 expected: Ok(Some(json!("foo"))),
792 expected_data: json!(["bar"]),
793 },
794 Test {
795 ptr: "/12a",
796 data: json!([]),
797 assign: json!("foo"),
798 expected: Err(Error::FailedToParseIndex {
799 position: 0,
800 offset: 0,
801 source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
802 source: "12a".into(),
803 offset: 2,
804 }),
805 }),
806 expected_data: json!([]),
807 },
808 Test {
809 ptr: "/002",
810 data: json!([]),
811 assign: json!("foo"),
812 expected: Err(Error::FailedToParseIndex {
813 position: 0,
814 offset: 0,
815 source: ParseIndexError::LeadingZeros,
816 }),
817 expected_data: json!([]),
818 },
819 Test {
820 ptr: "/+23",
821 data: json!([]),
822 assign: json!("foo"),
823 expected: Err(Error::FailedToParseIndex {
824 position: 0,
825 offset: 0,
826 source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
827 source: "+23".into(),
828 offset: 0,
829 }),
830 }),
831 expected_data: json!([]),
832 },
833 ]
834 .into_iter()
835 .enumerate()
836 .for_each(|(i, t)| t.run(i));
837 }
838
839 #[test]
840 #[cfg(feature = "toml")]
841 fn assign_toml() {
842 use toml::{toml, Table, Value};
843 [
844 Test {
845 data: Value::Table(toml::Table::new()),
846 ptr: "/foo",
847 assign: "bar".into(),
848 expected_data: toml! { "foo" = "bar" }.into(),
849 expected: Ok(None),
850 },
851 Test {
852 data: toml! {foo = "bar"}.into(),
853 ptr: "",
854 assign: "baz".into(),
855 expected_data: "baz".into(),
856 expected: Ok(Some(toml! {foo = "bar"}.into())),
857 },
858 Test {
859 data: toml! { foo = "bar"}.into(),
860 ptr: "/foo",
861 assign: "baz".into(),
862 expected_data: toml! {foo = "baz"}.into(),
863 expected: Ok(Some("bar".into())),
864 },
865 Test {
866 data: toml! { foo = "bar"}.into(),
867 ptr: "/foo/bar",
868 assign: "baz".into(),
869 expected_data: toml! {foo = { bar = "baz"}}.into(),
870 expected: Ok(Some("bar".into())),
871 },
872 Test {
873 data: Table::new().into(),
874 ptr: "/",
875 assign: "foo".into(),
876 expected_data: toml! {"" = "foo"}.into(),
877 expected: Ok(None),
878 },
879 Test {
880 data: Table::new().into(),
881 ptr: "/-",
882 assign: "foo".into(),
883 expected_data: toml! {"-" = "foo"}.into(),
884 expected: Ok(None),
885 },
886 Test {
887 data: "data".into(),
888 ptr: "/-",
889 assign: 34.into(),
890 expected_data: Value::Array(vec![34.into()]),
891 expected: Ok(Some("data".into())),
892 },
893 Test {
894 data: toml! {foo = "bar"}.into(),
895 ptr: "/foo/-",
896 assign: "baz".into(),
897 expected_data: toml! {foo = ["baz"]}.into(),
898 expected: Ok(Some("bar".into())),
899 },
900 Test {
901 data: Table::new().into(),
902 ptr: "/0",
903 assign: "foo".into(),
904 expected_data: toml! {"0" = "foo"}.into(),
905 expected: Ok(None),
906 },
907 Test {
908 data: 21.into(),
909 ptr: "/1",
910 assign: "foo".into(),
911 expected_data: toml! {"1" = "foo"}.into(),
912 expected: Ok(Some(21.into())),
913 },
914 Test {
915 data: Value::Array(vec![]),
916 ptr: "/0",
917 expected_data: vec![Value::from("foo")].into(),
918 assign: "foo".into(),
919 expected: Ok(None),
920 },
921 Test {
922 ptr: "/foo/-/bar",
923 assign: "baz".into(),
924 data: Table::new().into(),
925 expected: Ok(None),
926 expected_data: toml! { "foo" = [{"bar" = "baz"}] }.into(),
927 },
928 Test {
929 ptr: "/foo/-/bar",
930 assign: "qux".into(),
931 data: toml! {"foo" = [{"bar" = "baz"}] }.into(),
932 expected: Ok(None),
933 expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(),
934 },
935 Test {
936 ptr: "/foo/-/bar",
937 data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(),
938 assign: "quux".into(),
939 expected: Ok(None),
940 expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]}
941 .into(),
942 },
943 Test {
944 ptr: "/foo/0/bar",
945 data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(),
946 assign: "grault".into(),
947 expected: Ok(Some("baz".into())),
948 expected_data:
949 toml! {"foo" = [{"bar" = "grault"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(),
950 },
951 Test {
952 data: Value::Array(vec![]),
953 ptr: "/-",
954 assign: "foo".into(),
955 expected: Ok(None),
956 expected_data: vec!["foo"].into(),
957 },
958 Test {
959 data: Value::Array(vec![]),
960 ptr: "/1",
961 assign: "foo".into(),
962 expected: Err(Error::OutOfBounds {
963 position: 0,
964 offset: 0,
965 source: OutOfBoundsError {
966 index: 1,
967 length: 0,
968 },
969 }),
970 expected_data: Value::Array(vec![]),
971 },
972 Test {
973 data: Value::Array(vec![]),
974 ptr: "/a",
975 assign: "foo".into(),
976 expected: Err(Error::FailedToParseIndex {
977 position: 0,
978 offset: 0,
979 source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
980 source: "a".into(),
981 offset: 0,
982 }),
983 }),
984 expected_data: Value::Array(vec![]),
985 },
986 ]
987 .into_iter()
988 .enumerate()
989 .for_each(|(i, t)| t.run(i));
990 }
991}