1pub mod decode;
3pub mod parser;
4pub(crate) mod schema;
5pub(crate) mod walk;
6pub mod write;
7
8#[cfg(test)]
9mod test_parser;
10
11use std::{
12 collections::BTreeMap,
13 fmt::{self, Write as _},
14 rc::Rc,
15 sync::Arc,
16};
17
18use tracing::{trace, Level};
19
20use crate::{
21 warning::{self, IntoCaveat},
22 Verdict,
23};
24use parser::ErrorKind;
25use parser::{Parser, Span};
26
27#[doc(inline)]
28pub use parser::{line_col, Error, ErrorReport, LineCol};
29pub(crate) use parser::{parse, RawStr};
30
31const PATH_SEPARATOR: char = '.';
32const PATH_ROOT: &str = "$";
33
34#[doc(hidden)]
37#[macro_export]
38macro_rules! required_field_or_bail {
39 ($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {
40 match $fields.get($field_name) {
41 Some(field_elem) => field_elem,
42 None => {
43 $warnings.with_elem(
44 WarningKind::FieldRequired {
45 field_name: $field_name.into(),
46 },
47 $elem,
48 );
49 return Err($warnings);
50 }
51 }
52 };
53}
54
55#[doc(hidden)]
58#[macro_export]
59macro_rules! required_field {
60 ($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {{
61 let field = $fields.get($field_name);
62
63 if field.is_none() {
64 $warnings.with_elem(
65 WarningKind::FieldRequired {
66 field_name: $field_name.into(),
67 },
68 $elem,
69 );
70 }
71
72 field
73 }};
74}
75
76#[doc(hidden)]
79#[macro_export]
80macro_rules! expect_object_or_bail {
81 ($elem:expr, $warnings:expr) => {
82 match $elem.as_object_fields() {
83 Some(fields) => fields,
84 None => {
85 $warnings.with_elem(
86 WarningKind::FieldInvalidType {
87 expected_type: json::ValueKind::Object,
88 },
89 $elem,
90 );
91 return Err($warnings);
92 }
93 }
94 };
95}
96
97#[doc(hidden)]
100#[macro_export]
101macro_rules! expect_array_or_bail {
102 ($elem:expr, $warnings:expr) => {
103 match $elem.as_array() {
104 Some(fields) => fields,
105 None => {
106 $warnings.with_elem(
107 WarningKind::FieldInvalidType {
108 expected_type: json::ValueKind::Array,
109 },
110 $elem,
111 );
112 return Err($warnings);
113 }
114 }
115 };
116}
117
118#[doc(hidden)]
123#[macro_export]
124macro_rules! expect_string_or_bail {
125 ($elem:expr, $warnings:expr) => {{
126 use $crate::warning::GatherWarnings as _;
127
128 match $elem.as_raw_str() {
129 Some(s) => s.decode_escapes($elem).gather_warnings_into(&mut $warnings),
130 None => {
131 $warnings.with_elem(
132 WarningKind::FieldInvalidType {
133 expected_type: json::ValueKind::String,
134 },
135 $elem,
136 );
137 return Err($warnings);
138 }
139 }
140 }};
141}
142
143#[doc(hidden)]
147#[macro_export]
148macro_rules! parse_required_or_bail {
149 ($elem:expr, $fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
150 #[allow(
151 unused,
152 reason = "the macro uses the import but maybe the outside scope does too."
153 )]
154 use $crate::json::FromJson;
155 use $crate::warning::GatherWarnings as _;
156
157 let elem = required_field_or_bail!($elem, $fields, $elem_name, $warnings);
158 <$target as FromJson>::from_json(elem)?.gather_warnings_into(&mut $warnings)
159 }};
160}
161
162#[doc(hidden)]
165#[macro_export]
166macro_rules! parse_nullable_or_bail {
167 ($fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
168 #[allow(
169 unused,
170 reason = "the macro uses the import but maybe the outside scope does too."
171 )]
172 use $crate::json::FromJson as _;
173 use $crate::warning::GatherWarnings as _;
174
175 match $fields.get($elem_name) {
176 Some(elem) => Option::<$target>::from_json(elem)?.gather_warnings_into(&mut $warnings),
177 None => None,
178 }
179 }};
180}
181
182pub(crate) trait FromJson<'elem, 'buf>: Sized {
184 type WarningKind: warning::Kind;
185
186 fn from_json(elem: &'elem Element<'buf>) -> Verdict<Self, Self::WarningKind>;
188}
189
190impl<'a, 'b, T> FromJson<'a, 'b> for Option<T>
194where
195 T: FromJson<'a, 'b> + IntoCaveat,
196{
197 type WarningKind = T::WarningKind;
198
199 fn from_json(elem: &'a Element<'b>) -> Verdict<Self, Self::WarningKind> {
200 let value = elem.as_value();
201
202 if value.is_null() {
203 Ok(None.into_caveat(warning::Set::new()))
204 } else {
205 let v = T::from_json(elem)?;
206 Ok(v.map(Some))
207 }
208 }
209}
210
211#[derive(Clone, Debug, Eq, PartialEq)]
215pub struct Element<'buf> {
216 id: ElemId,
218
219 path_node: PathNodeRef<'buf>,
221
222 span: Span,
226
227 value: Value<'buf>,
229}
230
231#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
235pub struct ElemId(usize);
236
237impl fmt::Display for ElemId {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 fmt::Display::fmt(&self.0, f)
240 }
241}
242
243impl<'buf> Element<'buf> {
244 fn new(id: ElemId, path: PathNodeRef<'buf>, span: Span, value: Value<'buf>) -> Element<'buf> {
246 Element {
247 id,
248 path_node: path,
249 span,
250 value,
251 }
252 }
253
254 pub(crate) const fn id(&self) -> ElemId {
256 self.id
257 }
258
259 pub fn path(&self) -> PathRef<'buf> {
261 PathRef(self.path_node())
262 }
263
264 pub(crate) fn path_node(&self) -> PathNodeRef<'buf> {
266 Rc::clone(&self.path_node)
267 }
268
269 pub fn source_json(&self, source_json: &'buf str) -> SourceStr<'buf> {
283 if let PathNode::Object { key, .. } = *self.path_node {
284 let span = Span {
286 start: key.span().start,
287 end: self.span.end,
289 };
290 let field_str = &source_json
291 .get(span.start..span.end)
292 .expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR");
293 let field = RawStr::from_str(field_str, span);
294 let (key, value) = field_str
295 .split_once(':')
296 .expect("An objects field always contains a delimiting `:`");
297
298 SourceStr::Field { field, key, value }
299 } else {
300 let span = self.span;
301 let s = source_json
302 .get(span.start..span.end)
303 .expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR");
304 SourceStr::Value(RawStr::from_str(s, span))
305 }
306 }
307
308 pub fn source_json_value(&self, source_json: &'buf str) -> &'buf str {
321 source_json
322 .get(self.span.start..self.span.end)
323 .expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR")
324 }
325
326 pub(crate) fn value(&self) -> &Value<'buf> {
328 &self.value
329 }
330
331 pub(crate) fn as_value(&self) -> &Value<'buf> {
333 &self.value
334 }
335
336 pub(crate) fn as_raw_str(&self) -> Option<&RawStr<'buf>> {
338 self.value.as_raw_str()
339 }
340
341 pub(crate) fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
343 self.value.as_object_fields()
344 }
345
346 pub(crate) fn as_array(&self) -> Option<&[Element<'buf>]> {
347 self.value.as_array()
348 }
349
350 pub fn as_number_str(&self) -> Option<&str> {
351 self.value.as_number()
352 }
353}
354
355#[derive(Debug)]
356pub enum SourceStr<'buf> {
357 Value(RawStr<'buf>),
359
360 Field {
362 field: RawStr<'buf>,
364
365 key: &'buf str,
367
368 value: &'buf str,
370 },
371}
372
373impl fmt::Display for SourceStr<'_> {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 match self {
376 SourceStr::Value(s) => f.write_str(s.as_raw()),
377 SourceStr::Field { field, .. } => f.write_str(field.as_raw()),
378 }
379 }
380}
381
382impl PartialEq<&str> for SourceStr<'_> {
384 fn eq(&self, other: &&str) -> bool {
385 match self {
386 SourceStr::Value(s) => s.as_raw() == *other,
387 SourceStr::Field { field, .. } => field.as_raw() == *other,
388 }
389 }
390}
391
392impl PartialEq<String> for SourceStr<'_> {
394 fn eq(&self, other: &String) -> bool {
395 self.eq(&&**other)
396 }
397}
398
399impl PartialOrd for Element<'_> {
400 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
401 Some(self.cmp(other))
402 }
403}
404
405impl Ord for Element<'_> {
406 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
407 self.path_node.cmp(&other.path_node)
408 }
409}
410
411impl fmt::Display for Element<'_> {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 write!(f, "{} = {}", self.path_node, self.value)
414 }
415}
416
417#[derive(Clone, Debug, Eq, PartialEq)]
419pub(crate) struct Field<'buf>(Element<'buf>);
420
421impl<'buf> Field<'buf> {
422 #[expect(
423 clippy::unreachable,
424 reason = "A Field is created by the parser when the type is an Object."
425 )]
426 pub(crate) fn key(&self) -> RawStr<'buf> {
427 let PathNode::Object { key, .. } = *self.0.path_node else {
428 unreachable!();
429 };
430
431 key
432 }
433
434 #[expect(dead_code, reason = "pending use in `tariff::lint`")]
436 pub(crate) fn into_element(self) -> Element<'buf> {
437 self.0
438 }
439
440 pub(crate) fn element(&self) -> &Element<'buf> {
442 &self.0
443 }
444}
445
446#[derive(Clone, Debug, Eq, PartialEq)]
448pub(crate) enum Value<'buf> {
449 Null,
451
452 True,
454
455 False,
457
458 String(RawStr<'buf>),
460
461 Number(&'buf str),
466
467 Array(Vec<Element<'buf>>),
472
473 Object(Vec<Field<'buf>>),
479}
480
481impl<'buf> Value<'buf> {
482 pub(crate) fn kind(&self) -> ValueKind {
483 match self {
484 Value::Null => ValueKind::Null,
485 Value::True | Value::False => ValueKind::Bool,
486 Value::String(_) => ValueKind::String,
487 Value::Number(_) => ValueKind::Number,
488 Value::Array(_) => ValueKind::Array,
489 Value::Object(_) => ValueKind::Object,
490 }
491 }
492
493 pub(crate) fn is_null(&self) -> bool {
494 matches!(self, Value::Null)
495 }
496
497 pub(crate) fn is_scalar(&self) -> bool {
499 matches!(
500 self,
501 Value::Null | Value::True | Value::False | Value::String(_) | Value::Number(_)
502 )
503 }
504
505 pub(crate) fn as_array(&self) -> Option<&[Element<'buf>]> {
506 match self {
507 Value::Array(elems) => Some(elems),
508 _ => None,
509 }
510 }
511
512 pub(crate) fn as_number(&self) -> Option<&str> {
513 match self {
514 Value::Number(s) => Some(s),
515 _ => None,
516 }
517 }
518
519 pub(crate) fn as_raw_str(&self) -> Option<&RawStr<'buf>> {
521 match self {
522 Value::String(s) => Some(s),
523 _ => None,
524 }
525 }
526
527 pub(crate) fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
529 match self {
530 Value::Object(fields) => Some(fields),
531 _ => None,
532 }
533 }
534}
535
536impl fmt::Display for Value<'_> {
537 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538 match self {
539 Self::Null => write!(f, "null"),
540 Self::True => write!(f, "true"),
541 Self::False => write!(f, "false"),
542 Self::String(s) => write!(f, "{s}"),
543 Self::Number(s) => write!(f, "{s}"),
544 Self::Array(..) => f.write_str("[...]"),
545 Self::Object(..) => f.write_str("{...}"),
546 }
547 }
548}
549
550#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
552pub enum ValueKind {
553 Null,
554 Bool,
555 Number,
556 String,
557 Array,
558 Object,
559}
560
561impl fmt::Display for ValueKind {
562 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563 match self {
564 ValueKind::Null => write!(f, "null"),
565 ValueKind::Bool => write!(f, "bool"),
566 ValueKind::Number => write!(f, "number"),
567 ValueKind::String => write!(f, "string"),
568 ValueKind::Array => write!(f, "array"),
569 ValueKind::Object => write!(f, "object"),
570 }
571 }
572}
573
574#[derive(Copy, Clone, Debug, Eq, PartialEq)]
578pub(crate) enum ObjectKind {
579 Object,
580 Array,
581}
582
583pub type RawMap<'buf> = BTreeMap<RawStr<'buf>, Element<'buf>>;
584pub type RawRefMap<'a, 'buf> = BTreeMap<RawStr<'buf>, &'a Element<'buf>>;
585
586#[expect(dead_code, reason = "pending use in `tariff::lint`")]
587pub(crate) trait FieldsIntoExt<'buf> {
588 fn into_map(self) -> RawMap<'buf>;
589}
590
591pub(crate) trait FieldsAsExt<'buf> {
592 fn as_raw_map(&self) -> RawRefMap<'_, 'buf>;
593 fn find_field(&self, key: &str) -> Option<&Field<'buf>>;
594}
595
596impl<'buf> FieldsIntoExt<'buf> for Vec<Field<'buf>> {
597 fn into_map(self) -> RawMap<'buf> {
598 self.into_iter()
599 .map(|field| (field.key(), field.into_element()))
600 .collect()
601 }
602}
603
604impl<'buf> FieldsAsExt<'buf> for Vec<Field<'buf>> {
605 fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
606 self.iter()
607 .map(|field| (field.key(), field.element()))
608 .collect()
609 }
610
611 fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
612 self.iter().find(|field| field.key().as_raw() == key)
613 }
614}
615
616impl<'buf> FieldsAsExt<'buf> for [Field<'buf>] {
617 fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
618 self.iter()
619 .map(|field| (field.key(), field.element()))
620 .collect()
621 }
622
623 fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
624 self.iter().find(|field| field.key().as_raw() == key)
625 }
626}
627
628#[derive(Clone, Debug)]
631pub struct UnexpectedFields<'buf>(Vec<PathNodeRef<'buf>>);
632
633impl fmt::Display for UnexpectedFields<'_> {
634 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
635 if f.alternate() {
636 f.write_str("[\n")?;
638 for entry in &self.0 {
639 writeln!(f, "\t\"{entry}\",")?;
640 }
641 f.write_str("]\n")?;
642 } else {
643 f.write_char('[')?;
645 for entry in &self.0 {
646 write!(f, "{entry},")?;
647 }
648 f.write_char(']')?;
649 }
650
651 Ok(())
652 }
653}
654
655impl<'buf> UnexpectedFields<'buf> {
656 pub(crate) fn empty() -> Self {
658 Self(vec![])
659 }
660
661 pub(crate) fn from_vec(v: Vec<PathNodeRef<'buf>>) -> Self {
663 Self(v)
664 }
665
666 pub fn to_strings(&self) -> Vec<String> {
668 self.0.iter().map(ToString::to_string).collect()
669 }
670
671 pub fn into_strings(self) -> Vec<String> {
673 self.0.into_iter().map(|path| path.to_string()).collect()
674 }
675
676 pub fn is_empty(&self) -> bool {
678 self.0.is_empty()
679 }
680
681 pub fn len(&self) -> usize {
683 self.0.len()
684 }
685
686 pub fn iter<'a>(&'a self) -> UnexpectedFieldsIter<'a, 'buf> {
688 UnexpectedFieldsIter(self.0.iter())
689 }
690}
691
692impl<'buf> IntoIterator for UnexpectedFields<'buf> {
693 type Item = PathRef<'buf>;
694
695 type IntoIter = UnexpectedFieldsIntoIter<'buf>;
696
697 fn into_iter(self) -> Self::IntoIter {
698 UnexpectedFieldsIntoIter(self.0.into_iter())
699 }
700}
701
702pub struct UnexpectedFieldsIntoIter<'buf>(std::vec::IntoIter<PathNodeRef<'buf>>);
703
704impl<'buf> Iterator for UnexpectedFieldsIntoIter<'buf> {
705 type Item = PathRef<'buf>;
706
707 fn next(&mut self) -> Option<Self::Item> {
708 let path_node = self.0.next()?;
709
710 Some(PathRef(path_node))
711 }
712}
713
714impl<'a, 'buf> IntoIterator for &'a UnexpectedFields<'buf> {
715 type Item = PathRef<'buf>;
716
717 type IntoIter = UnexpectedFieldsIter<'a, 'buf>;
718
719 fn into_iter(self) -> Self::IntoIter {
720 self.iter()
721 }
722}
723
724pub struct UnexpectedFieldsIter<'a, 'buf>(std::slice::Iter<'a, PathNodeRef<'buf>>);
726
727impl<'buf> Iterator for UnexpectedFieldsIter<'_, 'buf> {
728 type Item = PathRef<'buf>;
729
730 fn next(&mut self) -> Option<Self::Item> {
731 let path_node = self.0.next()?;
732
733 Some(PathRef(Rc::clone(path_node)))
734 }
735}
736
737pub struct PathRef<'buf>(PathNodeRef<'buf>);
745
746impl fmt::Debug for PathRef<'_> {
747 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748 write!(f, "{self}")
749 }
750}
751
752impl<'buf> PathRef<'buf> {
753 pub fn components(&self) -> PathComponents<'buf> {
755 PathComponents(PathIter::new(Rc::clone(&self.0)))
756 }
757}
758
759pub struct PathComponents<'buf>(PathIter<'buf>);
761
762impl<'buf> Iterator for PathComponents<'buf> {
763 type Item = PathComponent<'buf>;
764
765 fn next(&mut self) -> Option<Self::Item> {
766 let path_node = self.0.next()?;
767 Some(PathComponent(path_node))
768 }
769}
770
771impl PartialEq<&str> for PathRef<'_> {
773 fn eq(&self, other: &&str) -> bool {
774 match_path_node(&self.0, other, |_| false)
775 }
776}
777
778impl PartialEq<String> for PathRef<'_> {
780 fn eq(&self, other: &String) -> bool {
781 match_path_node(&self.0, other, |_| false)
782 }
783}
784
785impl fmt::Display for PathRef<'_> {
786 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
787 fmt::Display::fmt(&self.0, f)
788 }
789}
790
791#[cfg(test)]
792mod test_path_node_matches_str {
793 use std::rc::Rc;
794
795 use crate::test;
796
797 use super::PathNode;
798
799 #[test]
800 fn should_match_path() {
801 test::setup();
802
803 let root = Rc::new(PathNode::Root);
804 let path_a = Rc::new(PathNode::Array {
805 parent: Rc::clone(&root),
806 index: 1,
807 });
808 let path_b = Rc::new(PathNode::Object {
809 parent: Rc::clone(&path_a),
810 key: r#""name""#.into(),
811 });
812 let path_c = PathNode::Object {
813 parent: Rc::clone(&path_b),
814 key: r#""gene""#.into(),
815 };
816
817 assert_eq!(*root, "$");
818 assert_eq!(*path_a, "$.1");
819 assert_eq!(*path_b, "$.1.name");
820 assert_eq!(path_c, "$.1.name.gene");
821 }
822}
823
824pub(crate) type PathNodeRef<'buf> = Rc<PathNode<'buf>>;
826
827#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
837pub(crate) enum PathNode<'buf> {
838 #[default]
840 Root,
841 Array {
843 parent: PathNodeRef<'buf>,
844 index: usize,
845 },
846 Object {
848 parent: PathNodeRef<'buf>,
849 key: RawStr<'buf>,
850 },
851}
852
853pub enum PathNodeKind {
856 Root,
858 Array,
860 Object,
862}
863
864#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
873pub struct Path(Vec<PathPiece>);
874
875impl Path {
876 const fn root() -> Self {
878 Self(vec![])
879 }
880
881 fn from_node(path: PathNodeRef<'_>) -> Self {
883 let paths: Vec<_> = PathIter::new(path).collect();
884
885 let pieces = paths
886 .into_iter()
887 .rev()
888 .filter_map(|path_node| match *path_node {
889 PathNode::Root => None,
890 PathNode::Array { index, .. } => Some(PathPiece::Array(index)),
891 PathNode::Object { key, .. } => Some(PathPiece::Object(key.to_string())),
892 })
893 .collect();
894
895 Self(pieces)
896 }
897}
898
899impl PartialEq<&str> for Path {
901 fn eq(&self, other: &&str) -> bool {
902 match_path(self, other)
903 }
904}
905
906impl PartialEq<String> for Path {
908 fn eq(&self, other: &String) -> bool {
909 match_path(self, other)
910 }
911}
912
913impl fmt::Display for Path {
914 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
915 let iter = self.0.iter();
916
917 write!(f, "$")?;
918
919 for path in iter {
920 write!(f, ".{path}")?;
921 }
922
923 Ok(())
924 }
925}
926
927#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
931enum PathPiece {
932 Array(usize),
934 Object(String),
936}
937
938impl fmt::Display for PathPiece {
939 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
940 match self {
941 PathPiece::Array(index) => write!(f, "{index}"),
942 PathPiece::Object(key) => write!(f, "{key}"),
943 }
944 }
945}
946
947pub struct PathComponent<'buf>(PathNodeRef<'buf>);
949
950impl PathComponent<'_> {
951 pub fn kind(&self) -> PathNodeKind {
953 match *self.0 {
954 PathNode::Root => PathNodeKind::Root,
955 PathNode::Array { .. } => PathNodeKind::Array,
956 PathNode::Object { .. } => PathNodeKind::Object,
957 }
958 }
959}
960
961impl fmt::Display for PathComponent<'_> {
962 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963 match *self.0 {
964 PathNode::Root => f.write_str(PATH_ROOT),
965 PathNode::Array { index, .. } => write!(f, "{index}"),
966 PathNode::Object { key, .. } => write!(f, "{key}"),
967 }
968 }
969}
970
971impl fmt::Display for PathNode<'_> {
972 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
973 let paths: Vec<_> = PathIter::new(Rc::new(self.clone())).collect();
974 let mut iter = paths.into_iter().rev();
975
976 if f.alternate() {
977 for path in iter {
979 match *path {
980 PathNode::Root => f.write_str("")?,
981 PathNode::Array { .. } | PathNode::Object { .. } => f.write_str("...|")?,
982 }
983 }
984 } else {
985 if let Some(path) = iter.next() {
986 write!(f, "{}", PathComponent(path))?;
987 }
988
989 for path in iter {
990 write!(f, ".{}", PathComponent(path))?;
991 }
992 }
993 Ok(())
994 }
995}
996
997impl<'buf> PathNode<'buf> {
998 pub(crate) fn is_root(&self) -> bool {
1000 matches!(self, PathNode::Root)
1001 }
1002
1003 pub(crate) fn is_array(&self) -> bool {
1005 matches!(self, PathNode::Array { .. })
1006 }
1007
1008 pub(crate) fn as_object_key(&self) -> Option<&RawStr<'buf>> {
1010 match self {
1011 PathNode::Object { key, .. } => Some(key),
1012 PathNode::Root | PathNode::Array { .. } => None,
1013 }
1014 }
1015}
1016
1017fn match_path_node<F>(path: &PathNode<'_>, s: &str, mut skip: F) -> bool
1023where
1024 F: FnMut(&str) -> bool,
1025{
1026 let mut parts = s.rsplit(PATH_SEPARATOR);
1027 let mut paths_iter = PathIter::new(Rc::new(path.clone()));
1028
1029 loop {
1030 let node_segment = paths_iter.next();
1031 let str_segment = parts.next();
1032
1033 let (node_segment, str_segment) = match (node_segment, str_segment) {
1034 (None, None) => return true,
1036 (None, Some(_)) | (Some(_), None) => return false,
1038 (Some(a), Some(b)) => (a, b),
1040 };
1041
1042 if skip(str_segment) {
1044 continue;
1045 }
1046
1047 let yip = match *node_segment {
1048 PathNode::Root => str_segment == PATH_ROOT,
1049 PathNode::Array { index, .. } => {
1050 let Ok(b) = str_segment.parse::<usize>() else {
1051 return false;
1052 };
1053
1054 index == b
1055 }
1056 PathNode::Object { key, .. } => key.as_raw() == str_segment,
1057 };
1058
1059 if !yip {
1061 return false;
1062 }
1063 }
1064}
1065
1066fn match_path(path: &Path, s: &str) -> bool {
1068 let mut parts = s.split(PATH_SEPARATOR);
1069 let mut paths_iter = path.0.iter();
1070
1071 let Some(str_segment) = parts.next() else {
1072 return false;
1073 };
1074
1075 if str_segment != PATH_ROOT {
1078 return false;
1079 }
1080
1081 loop {
1082 let node_segment = paths_iter.next();
1083 let str_segment = parts.next();
1084
1085 let (node_segment, str_segment) = match (node_segment, str_segment) {
1086 (None, None) => return true,
1088 (None, Some(_)) | (Some(_), None) => return false,
1090 (Some(a), Some(b)) => (a, b),
1092 };
1093
1094 let yip = match node_segment {
1095 PathPiece::Array(index) => {
1096 let Ok(b) = str_segment.parse::<usize>() else {
1097 return false;
1098 };
1099
1100 *index == b
1101 }
1102 PathPiece::Object(key) => key == str_segment,
1103 };
1104
1105 if !yip {
1107 return false;
1108 }
1109 }
1110}
1111
1112impl PartialEq<&str> for PathNode<'_> {
1113 fn eq(&self, other: &&str) -> bool {
1114 match_path_node(self, other, |_| false)
1115 }
1116}
1117
1118impl PartialEq<String> for PathNode<'_> {
1119 fn eq(&self, other: &String) -> bool {
1120 match_path_node(self, other, |_| false)
1121 }
1122}
1123
1124struct PathIter<'buf> {
1126 complete: bool,
1128 path: PathNodeRef<'buf>,
1130}
1131
1132impl<'buf> PathIter<'buf> {
1133 fn new(path: PathNodeRef<'buf>) -> Self {
1135 Self {
1136 complete: false,
1137 path,
1138 }
1139 }
1140}
1141
1142impl<'buf> Iterator for PathIter<'buf> {
1143 type Item = PathNodeRef<'buf>;
1144
1145 fn next(&mut self) -> Option<Self::Item> {
1146 if self.complete {
1147 return None;
1148 }
1149
1150 match &*self.path {
1151 PathNode::Root => {
1152 self.complete = true;
1154 Some(Rc::clone(&self.path))
1155 }
1156 PathNode::Array { parent, .. } | PathNode::Object { parent, .. } => {
1157 let next = Rc::clone(&self.path);
1158 self.path = Rc::clone(parent);
1159 Some(next)
1160 }
1161 }
1162 }
1163}
1164
1165struct DisplayExpectStack<'a>(&'a [schema::Expect]);
1167
1168impl fmt::Display for DisplayExpectStack<'_> {
1169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1170 let mut iter = self.0.iter().rev();
1171 let last = iter.next();
1172
1173 f.write_str("~")?;
1175
1176 for _ in iter {
1177 f.write_str("...~")?;
1178 }
1179
1180 if let Some(exp) = last {
1181 match exp {
1182 schema::Expect::Scalar => f.write_str("~")?,
1183 schema::Expect::Array(element) => match &**element {
1184 schema::Element::Scalar => f.write_str("~")?,
1185 schema::Element::Array(element) => write!(f, "[{element:?}]")?,
1186 schema::Element::Object(fields) => {
1187 write!(f, "[{{{:}}}])", DisplayExpectFields(&**fields))?;
1188 }
1189 },
1190 schema::Expect::Object(fields) => {
1191 write!(f, "{{{:}}}", DisplayExpectFields(&**fields))?;
1192 }
1193 schema::Expect::UnmatchedScalar => write!(f, "unmatched(scalar)")?,
1194 schema::Expect::UnmatchedArray => write!(f, "unmatched(array)")?,
1195 schema::Expect::UnmatchedObject => write!(f, "unmatched(object)")?,
1196 schema::Expect::OutOfSchema => write!(f, "no_schema")?,
1197 }
1198 }
1199
1200 Ok(())
1201 }
1202}
1203
1204struct DisplayExpectFields<'a, V>(&'a BTreeMap<&'a str, V>);
1206
1207impl<V> fmt::Display for DisplayExpectFields<'_, V> {
1208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1209 const MAX_FIELDS: usize = 8;
1210
1211 let mut count = 0;
1212 let mut iter = self.0.keys().peekable();
1213
1214 loop {
1215 if count >= MAX_FIELDS {
1216 f.write_str("...")?;
1217 break;
1218 }
1219
1220 let Some(field) = iter.next() else {
1221 break;
1222 };
1223
1224 count += 1;
1225 write!(f, "{field}")?;
1226
1227 let Some(_) = iter.peek() else {
1228 break;
1229 };
1230
1231 f.write_str(", ")?;
1232 }
1233
1234 Ok(())
1235 }
1236}
1237
1238#[derive(Debug)]
1239struct UnbalancedExpectStack;
1240
1241impl fmt::Display for UnbalancedExpectStack {
1242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1243 f.write_str("unbalanced expectation stack")
1244 }
1245}
1246
1247impl std::error::Error for UnbalancedExpectStack {}
1248
1249pub(crate) fn parse_with_schema<'buf>(
1252 json: &'buf str,
1253 schema: &schema::Element,
1254) -> Result<ParseReport<'buf>, Error> {
1255 let parser = Parser::new(json);
1256 let mut unexpected_fields = vec![];
1257 let mut expectation_stack = vec![schema.to_expectation()];
1259
1260 for event in parser {
1261 match event? {
1262 parser::Event::Open { kind, parent_path } => {
1263 let Some(expectation) = expectation_stack.pop() else {
1266 return Err(ErrorKind::Internal(Box::new(UnbalancedExpectStack))
1267 .into_partial_error_without_token()
1268 .with_root_path());
1269 };
1270
1271 if tracing::enabled!(Level::DEBUG) {
1272 match kind {
1273 ObjectKind::Array => {
1274 trace!("{parent_path} [ {}", DisplayExpectStack(&expectation_stack));
1275 }
1276 ObjectKind::Object => trace!(
1277 "{parent_path} {{ {}",
1278 DisplayExpectStack(&expectation_stack)
1279 ),
1280 }
1281 }
1282
1283 match expectation {
1284 schema::Expect::Array(elem) => {
1285 if parent_path.is_root() {
1288 let next = match kind {
1289 ObjectKind::Array => schema::Expect::Array(elem),
1290 ObjectKind::Object => schema::Expect::UnmatchedArray,
1291 };
1292
1293 expectation_stack.push(next);
1294 trace!("{}", DisplayExpectStack(&expectation_stack));
1295 continue;
1296 }
1297
1298 if !parent_path.is_array() {
1299 expectation_stack.push(schema::Expect::UnmatchedArray);
1300 trace!("{}", DisplayExpectStack(&expectation_stack));
1301 continue;
1302 }
1303
1304 expectation_stack.push(schema::Expect::Array(Arc::clone(&elem)));
1305 expectation_stack.push(elem.to_expectation());
1307 }
1308 schema::Expect::Object(fields) => {
1309 if parent_path.is_root() {
1312 let next = match kind {
1313 ObjectKind::Array => schema::Expect::UnmatchedObject,
1314 ObjectKind::Object => schema::Expect::Object(fields),
1315 };
1316
1317 expectation_stack.push(next);
1318 trace!("{}", DisplayExpectStack(&expectation_stack));
1319 continue;
1320 }
1321 let Some(key) = parent_path.as_object_key() else {
1322 expectation_stack.push(schema::Expect::UnmatchedObject);
1323 trace!("{}", DisplayExpectStack(&expectation_stack));
1324 continue;
1325 };
1326
1327 let next = if let Some(elem) = fields.get(key.as_raw()) {
1328 open_object(kind, elem.as_ref())
1329 } else {
1330 unexpected_fields.push(parent_path);
1331 schema::Expect::OutOfSchema
1332 };
1333
1334 expectation_stack.push(schema::Expect::Object(fields));
1335 expectation_stack.push(next);
1336 }
1337 schema::Expect::OutOfSchema => {
1338 expectation_stack.push(expectation);
1346 expectation_stack.push(schema::Expect::OutOfSchema);
1347 }
1348 schema::Expect::UnmatchedArray | schema::Expect::UnmatchedObject => {
1349 expectation_stack.push(expectation);
1350 expectation_stack.push(schema::Expect::OutOfSchema);
1351 }
1352 _ => {
1353 expectation_stack.push(expectation);
1354 }
1355 }
1356
1357 trace!("{}", DisplayExpectStack(&expectation_stack));
1358 }
1359 parser::Event::Element { kind, parent_path } => {
1360 let Some(expectation) = expectation_stack.pop() else {
1363 return Err(ErrorKind::Internal(Box::new(UnbalancedExpectStack))
1364 .into_partial_error_without_token()
1365 .with_root_path());
1366 };
1367
1368 if let ValueKind::Array | ValueKind::Object = kind {
1371 if tracing::enabled!(Level::DEBUG) {
1372 match kind {
1373 ValueKind::Array => {
1374 trace!(
1375 "{parent_path} ] {}",
1376 DisplayExpectStack(&expectation_stack)
1377 );
1378 }
1379 ValueKind::Object => trace!(
1380 "{parent_path} }} {}",
1381 DisplayExpectStack(&expectation_stack)
1382 ),
1383 _ => (),
1384 }
1385 }
1386 continue;
1387 }
1388
1389 match expectation {
1390 #[expect(
1391 clippy::unreachable,
1392 reason = "The parser only emits an `Event::Complete` for a scalar object at the root"
1393 )]
1394 schema::Expect::Object(fields) => match &*parent_path {
1395 PathNode::Root => unreachable!(),
1396 PathNode::Array { .. } => {
1397 expectation_stack.push(schema::Expect::UnmatchedObject);
1398 }
1399 PathNode::Object { parent, key } => {
1400 trace!("{parent:#}.{key}");
1401
1402 if !fields.contains_key(key.as_raw()) {
1403 unexpected_fields.push(parent_path);
1404 }
1405
1406 expectation_stack.push(schema::Expect::Object(fields));
1407 }
1408 },
1409 schema::Expect::OutOfSchema => {
1410 unexpected_fields.push(parent_path);
1411 expectation_stack.push(expectation);
1412 }
1413 _ => {
1414 expectation_stack.push(expectation);
1415 }
1416 }
1417 }
1418 parser::Event::Complete(element) => {
1419 if element.value().is_scalar() {
1420 unexpected_fields.push(element.path_node());
1421 }
1422
1423 return Ok(ParseReport {
1426 element,
1427 unexpected_fields: UnexpectedFields::from_vec(unexpected_fields),
1428 });
1429 }
1430 }
1431 }
1432
1433 Err(ErrorKind::UnexpectedEOF
1434 .into_partial_error_without_token()
1435 .with_root_path())
1436}
1437
1438fn open_object(kind: ObjectKind, elem: Option<&Arc<schema::Element>>) -> schema::Expect {
1439 let Some(schema) = elem else {
1440 return schema::Expect::OutOfSchema;
1441 };
1442
1443 match (kind, &**schema) {
1444 (ObjectKind::Object | ObjectKind::Array, schema::Element::Scalar) => {
1445 schema::Expect::UnmatchedScalar
1446 }
1447 (ObjectKind::Object, schema::Element::Array(_)) => schema::Expect::UnmatchedArray,
1448 (ObjectKind::Object, schema::Element::Object(fields)) => {
1449 schema::Expect::Object(Arc::clone(fields))
1450 }
1451 (ObjectKind::Array, schema::Element::Array(element)) => {
1452 schema::Expect::Array(Arc::clone(element))
1453 }
1454 (ObjectKind::Array, schema::Element::Object(_)) => schema::Expect::UnmatchedObject,
1455 }
1456}
1457
1458#[derive(Debug)]
1461pub(crate) struct ParseReport<'buf> {
1462 pub element: Element<'buf>,
1464
1465 pub unexpected_fields: UnexpectedFields<'buf>,
1467}
1468
1469#[cfg(test)]
1470pub mod test {
1471 #![allow(clippy::missing_panics_doc, reason = "tests are allowed to panic")]
1472 #![allow(clippy::panic, reason = "tests are allowed panic")]
1473
1474 use crate::{json::match_path_node, test::Expectation};
1475
1476 use super::{
1477 parser::Span, walk::DepthFirst, ElemId, Element, Field, FieldsAsExt as _, PathNode,
1478 PathNodeRef, PathRef, RawStr, UnexpectedFields, Value,
1479 };
1480
1481 impl<'buf> Element<'buf> {
1482 pub fn span(&self) -> Span {
1484 self.span
1485 }
1486
1487 pub(crate) fn into_value(self) -> Value<'buf> {
1489 self.value
1490 }
1491
1492 pub(crate) fn into_parts(self) -> (ElemId, PathNodeRef<'buf>, Span, Value<'buf>) {
1494 let Self {
1495 id,
1496 path_node: path,
1497 span,
1498 value,
1499 } = self;
1500 (id, path, span, value)
1501 }
1502
1503 pub(crate) fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
1504 self.as_object_fields()
1505 .and_then(|fields| fields.find_field(key))
1506 }
1507 }
1508
1509 impl<'buf> Value<'buf> {
1510 pub(crate) fn is_array(&self) -> bool {
1512 matches!(self, Value::Array(_))
1513 }
1514
1515 pub(crate) fn is_object(&self) -> bool {
1517 matches!(self, Value::Object(_))
1518 }
1519
1520 pub(crate) fn as_string(&self) -> Option<&RawStr<'buf>> {
1521 match self {
1522 Value::String(s) => Some(s),
1523 _ => None,
1524 }
1525 }
1526 }
1527
1528 impl<'buf> Field<'buf> {
1529 pub fn id(&self) -> ElemId {
1530 self.0.id()
1531 }
1532
1533 pub fn into_parts(self) -> (ElemId, PathNodeRef<'buf>, Span, Value<'buf>) {
1534 self.0.into_parts()
1535 }
1536 }
1537
1538 impl<'buf> UnexpectedFields<'buf> {
1539 pub(super) fn into_inner(self) -> Vec<PathNodeRef<'buf>> {
1541 self.0
1542 }
1543
1544 fn filter_matches(&mut self, glob: &PathGlob) {
1546 self.0.retain(|path| !glob.matches(path));
1547 }
1548 }
1549
1550 #[derive(Debug)]
1553 pub(crate) struct PathGlob(String);
1554
1555 impl PathGlob {
1556 pub(crate) fn matches(&self, path: &PathNode<'_>) -> bool {
1558 const WILDCARD: &str = "*";
1559
1560 match_path_node(path, &self.0, |s| {
1561 s == WILDCARD
1563 })
1564 }
1565 }
1566
1567 impl From<usize> for ElemId {
1569 fn from(value: usize) -> Self {
1570 Self(value)
1571 }
1572 }
1573
1574 impl<'a> From<&'a str> for PathGlob {
1575 fn from(s: &'a str) -> Self {
1576 Self(s.into())
1577 }
1578 }
1579
1580 impl From<String> for PathGlob {
1581 fn from(s: String) -> Self {
1582 Self(s)
1583 }
1584 }
1585
1586 impl<'de> serde::Deserialize<'de> for PathGlob {
1587 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1588 where
1589 D: ::serde::Deserializer<'de>,
1590 {
1591 let s = <String as ::serde::Deserialize>::deserialize(deserializer)?;
1592 Ok(Self(s))
1593 }
1594 }
1595
1596 pub struct ElementMap<'a, 'buf>(Vec<&'a Element<'buf>>);
1598
1599 impl<'a, 'buf> ElementMap<'a, 'buf> {
1600 pub fn for_elem(root: &'a Element<'buf>) -> Self {
1602 let walker = DepthFirst::new(root);
1604 Self(walker.collect())
1605 }
1606
1607 pub fn get(&self, id: ElemId) -> &Element<'buf> {
1609 self.0.get(id.0).map(|e| &**e).unwrap()
1610 }
1611
1612 pub fn path(&self, id: ElemId) -> PathRef<'buf> {
1614 self.0.get(id.0).map(|elem| elem.path()).unwrap()
1615 }
1616 }
1617
1618 #[track_caller]
1620 pub(crate) fn expect_no_unexpected_fields(
1621 expect_file_name: &str,
1622 unexpected_fields: &UnexpectedFields<'_>,
1623 ) {
1624 if !unexpected_fields.is_empty() {
1625 const MAX_FIELD_DISPLAY: usize = 20;
1626
1627 if unexpected_fields.len() > MAX_FIELD_DISPLAY {
1628 let truncated_fields = unexpected_fields
1629 .iter()
1630 .take(MAX_FIELD_DISPLAY)
1631 .map(|path| path.to_string())
1632 .collect::<Vec<_>>();
1633
1634 panic!(
1635 "The expect file `{expect_file_name}` didn't expect `{}` unexpected fields;\n\
1636 displaying the first ({}):\n{}\n... and {} more",
1637 unexpected_fields.len(),
1638 truncated_fields.len(),
1639 truncated_fields.join(",\n"),
1640 unexpected_fields.len() - truncated_fields.len(),
1641 )
1642 } else {
1643 panic!(
1644 "The expect file `{expect_file_name}` didn't expect `{}` unexpected fields:\n{}",
1645 unexpected_fields.len(),
1646 unexpected_fields.to_strings().join(",\n")
1647 )
1648 };
1649 }
1650 }
1651
1652 #[track_caller]
1656 pub(crate) fn expect_unexpected_fields(
1657 expect_file_name: &str,
1658 unexpected_fields: &mut UnexpectedFields<'_>,
1659 expected: Expectation<Vec<PathGlob>>,
1660 ) {
1661 if let Expectation::Present(expectation) = expected {
1662 let unexpected_fields_expect = expectation.expect_value();
1663
1664 for expect_glob in unexpected_fields_expect {
1667 unexpected_fields.filter_matches(&expect_glob);
1668 }
1669
1670 expect_no_unexpected_fields(expect_file_name, unexpected_fields);
1671 } else {
1672 expect_no_unexpected_fields(expect_file_name, unexpected_fields);
1673 }
1674 }
1675
1676 #[cfg(test)]
1677 mod test_path_matches_glob {
1678 use std::rc::Rc;
1679
1680 use crate::test;
1681
1682 use super::{PathGlob, PathNode};
1683
1684 #[test]
1685 fn should_match_path() {
1686 test::setup();
1687
1688 let root = Rc::new(PathNode::Root);
1689 let path_a = Rc::new(PathNode::Array {
1690 parent: Rc::clone(&root),
1691 index: 1,
1692 });
1693 let path_b = Rc::new(PathNode::Object {
1694 parent: Rc::clone(&path_a),
1695 key: r#""name""#.into(),
1696 });
1697 let path_c = PathNode::Object {
1698 parent: Rc::clone(&path_b),
1699 key: r#""gene""#.into(),
1700 };
1701
1702 assert!(PathGlob::from("$").matches(&root));
1703 assert!(PathGlob::from("*").matches(&root));
1704
1705 assert!(!PathGlob::from("*").matches(&path_a));
1706 assert!(PathGlob::from("*.*").matches(&path_a));
1707 assert!(PathGlob::from("$.*").matches(&path_a));
1708 assert!(PathGlob::from("$.1").matches(&path_a));
1709
1710 assert!(!PathGlob::from("*").matches(&path_b));
1711 assert!(!PathGlob::from("*.*").matches(&path_b));
1712 assert!(PathGlob::from("*.*.*").matches(&path_b));
1713 assert!(PathGlob::from("$.*.*").matches(&path_b));
1714 assert!(PathGlob::from("$.1.*").matches(&path_b));
1715 assert!(PathGlob::from("$.*.name").matches(&path_b));
1716 assert!(PathGlob::from("$.1.name").matches(&path_b));
1717
1718 assert!(PathGlob::from("$.1.name.gene").matches(&path_c));
1719 }
1720 }
1721}
1722
1723#[cfg(test)]
1724mod test_path {
1725 use super::{Path, PathPiece};
1726
1727 #[test]
1728 fn path_should_cmp_with_str() {
1729 assert_ne!(Path::root(), "");
1730 assert_eq!(Path::root(), "$");
1731 assert_eq!(Path(vec![PathPiece::Object("field_a".into())]), "$.field_a");
1732 assert_eq!(Path(vec![PathPiece::Array(1)]), "$.1");
1733 assert_eq!(
1734 Path(vec![
1735 PathPiece::Object("field_a".into()),
1736 PathPiece::Array(1)
1737 ]),
1738 "$.field_a.1"
1739 );
1740 }
1741
1742 #[test]
1743 fn path_should_display() {
1744 assert_eq!(Path::root().to_string(), "$");
1745 assert_eq!(
1746 Path(vec![PathPiece::Object("field_a".into())]).to_string(),
1747 "$.field_a"
1748 );
1749 assert_eq!(Path(vec![PathPiece::Array(1)]).to_string(), "$.1");
1750 assert_eq!(
1751 Path(vec![
1752 PathPiece::Object("field_a".into()),
1753 PathPiece::Array(1)
1754 ])
1755 .to_string(),
1756 "$.field_a.1"
1757 );
1758 }
1759}
1760
1761#[cfg(test)]
1762mod test_parse_with_schema {
1763 use crate::{json_schema, test};
1764
1765 use super::{parse_with_schema, ParseReport};
1766
1767 #[test]
1768 fn should_report_unexpected_fields_for_root_element() {
1769 const JSON: &str = "null";
1770
1771 test::setup();
1772
1773 let schema = json_schema!({
1774 "id",
1775 "currency",
1776 });
1777
1778 let report = parse_with_schema(JSON, &schema).unwrap();
1779 let ParseReport {
1780 element: _,
1781 unexpected_fields,
1782 } = report;
1783
1784 {
1785 let [field_a] = unexpected_fields.into_inner().try_into().unwrap();
1786 assert_eq!(*field_a, "$");
1787 }
1788 }
1789
1790 #[test]
1791 fn should_report_unexpected_fields_in_flat_object() {
1792 const JSON: &str = r#"{
1793 "id": "tariff_id",
1794 "currency": "EUR",
1795 "name": "Barry",
1796 "address": "Barrystown"
1797}"#;
1798
1799 test::setup();
1800
1801 let schema = json_schema!({
1802 "id",
1803 "currency",
1804 });
1805
1806 let report = parse_with_schema(JSON, &schema).unwrap();
1807 let ParseReport {
1808 element: _,
1809 unexpected_fields,
1810 } = report;
1811
1812 {
1813 let [field_a, field_b] = unexpected_fields.into_inner().try_into().unwrap();
1814 assert_eq!(*field_a, "$.name");
1815 assert_eq!(*field_b, "$.address");
1816 }
1817 }
1818
1819 #[test]
1820 fn should_report_unexpected_fields_in_nested_object() {
1821 const JSON: &str = r#"{
1822 "id": "tariff_id",
1823 "currency": "EUR",
1824 "owner": {
1825 "id": "456856",
1826 "subscription_id": "tedi4568",
1827 "name": "Barry",
1828 "address": "Barrystown"
1829 }
1830}"#;
1831
1832 test::setup();
1833
1834 let schema = json_schema!({
1835 "id",
1836 "currency",
1837 "owner": {
1838 "id",
1839 "subscription_id"
1840 }
1841 });
1842
1843 let report = parse_with_schema(JSON, &schema).unwrap();
1844 let ParseReport {
1845 element: _,
1846 unexpected_fields,
1847 } = report;
1848
1849 {
1850 let [field_a, field_b] = unexpected_fields.into_inner().try_into().unwrap();
1851 assert_eq!(*field_a, "$.owner.name");
1852 assert_eq!(*field_b, "$.owner.address");
1853 }
1854 }
1855
1856 #[test]
1857 fn should_parse_nested_object_out_of_schema() {
1858 const JSON: &str = r#"{
1859 "id": "tariff_id",
1860 "owner": {
1861 "id": "456856",
1862 "subscription_id": "tedi4568",
1863 "name": "Barry",
1864 "address": {
1865 "city": "Barrystown",
1866 "street": "Barrysstreet"
1867 }
1868 },
1869 "currency": "EUR",
1870 "country": "NL"
1871}"#;
1872
1873 test::setup();
1874
1875 let schema = json_schema!({
1876 "id",
1877 "currency",
1878 "owner"
1879 });
1880
1881 let report = parse_with_schema(JSON, &schema).unwrap();
1882 let ParseReport {
1883 element: _,
1884 unexpected_fields,
1885 } = report;
1886
1887 {
1888 let [field_a, field_b, field_c, field_d, field_e, field_f] =
1889 unexpected_fields.into_inner().try_into().unwrap();
1890 assert_eq!(*field_a, "$.owner.id");
1891 assert_eq!(*field_b, "$.owner.subscription_id");
1892 assert_eq!(*field_c, "$.owner.name");
1893 assert_eq!(*field_d, "$.owner.address.city");
1894 assert_eq!(*field_e, "$.owner.address.street");
1895 assert_eq!(*field_f, "$.country");
1896 }
1897 }
1898
1899 #[test]
1900 fn should_report_unexpected_fields_in_array_with_nested_object() {
1901 const JSON: &str = r#"{
1902 "id": "tariff_id",
1903 "currency": "EUR",
1904 "elements": [{
1905 "id": "456856",
1906 "subscription_id": "tedi4568",
1907 "name": "Barry",
1908 "address": "Barrystown"
1909 }]
1910}"#;
1911
1912 test::setup();
1913
1914 let schema = json_schema!({
1915 "id",
1916 "currency",
1917 "elements": [{
1918 "id",
1919 "subscription_id"
1920 }]
1921 });
1922
1923 let report = parse_with_schema(JSON, &schema).unwrap();
1924 let ParseReport {
1925 element: _,
1926 unexpected_fields,
1927 } = report;
1928
1929 {
1930 let [field_a, field_b] = unexpected_fields.into_inner().try_into().unwrap();
1931 assert_eq!(*field_a, "$.elements.0.name");
1932 assert_eq!(*field_b, "$.elements.0.address");
1933 }
1934 }
1935
1936 #[test]
1937 fn should_report_unexpected_fields_in_array_of_nested_objects() {
1938 const JSON: &str = r#"{
1939 "id": "tariff_id",
1940 "currency": "EUR",
1941 "elements": [
1942 {
1943 "id": "456856",
1944 "subscription_id": "tedi4568",
1945 "name": "Barry",
1946 "address": "Barrystown"
1947 },
1948 {
1949 "id": "8746we",
1950 "subscription_id": "dfr345",
1951 "name": "Gerry",
1952 "address": "Gerrystown"
1953 }
1954 ]
1955}"#;
1956
1957 test::setup();
1958
1959 let schema = json_schema!({
1960 "id",
1961 "currency",
1962 "elements": [{
1963 "id",
1964 "subscription_id"
1965 }]
1966 });
1967
1968 let report = parse_with_schema(JSON, &schema).unwrap();
1969 let ParseReport {
1970 element: _,
1971 unexpected_fields,
1972 } = report;
1973
1974 {
1975 let [field_a, field_b, field_c, field_d] =
1976 unexpected_fields.into_inner().try_into().unwrap();
1977 assert_eq!(*field_a, "$.elements.0.name");
1978 assert_eq!(*field_b, "$.elements.0.address");
1979 assert_eq!(*field_c, "$.elements.1.name");
1980 assert_eq!(*field_d, "$.elements.1.address");
1981 }
1982 }
1983
1984 #[test]
1985 fn should_report_unexpected_fields_in_array_of_objects() {
1986 const JSON: &str = r#"[
1987 {
1988 "id": "456856",
1989 "subscription_id": "tedi4568",
1990 "name": "Barry",
1991 "address": "Barrystown"
1992 },
1993 {
1994 "id": "8746we",
1995 "subscription_id": "dfr345",
1996 "name": "Gerry",
1997 "address": "Gerrystown"
1998 }
1999]"#;
2000
2001 test::setup();
2002
2003 let schema = json_schema!([
2004 {
2005 "id",
2006 "subscription_id"
2007 }
2008 ]);
2009
2010 let report = parse_with_schema(JSON, &schema).unwrap();
2011 let ParseReport {
2012 element: _,
2013 unexpected_fields,
2014 } = report;
2015
2016 {
2017 let [field_a, field_b, field_c, field_d] =
2018 unexpected_fields.into_inner().try_into().unwrap();
2019 assert_eq!(*field_a, "$.0.name");
2020 assert_eq!(*field_b, "$.0.address");
2021 assert_eq!(*field_c, "$.1.name");
2022 assert_eq!(*field_d, "$.1.address");
2023 }
2024 }
2025}
2026
2027#[cfg(test)]
2028mod test_source_json {
2029 use super::{parse, walk};
2030
2031 #[test]
2032 fn should_resolve_to_source_json() {
2033 const JSON: &str = r#"{
2034 "name": "David Byrne",
2035 "hobbies": ["song writing", "thinking about society"]
2036}"#;
2037
2038 let element = parse(JSON).unwrap();
2039
2040 let mut walk = walk::DepthFirst::new(&element);
2041
2042 let root = walk.next().unwrap();
2043 assert_eq!(root.source_json(JSON), JSON);
2044
2045 let field_name = walk.next().unwrap();
2046 assert_eq!(field_name.source_json(JSON), r#""name": "David Byrne""#);
2047 assert_eq!(field_name.source_json_value(JSON), r#""David Byrne""#);
2048
2049 let field_hobbies = walk.next().unwrap();
2050 assert_eq!(
2051 field_hobbies.source_json(JSON),
2052 r#""hobbies": ["song writing", "thinking about society"]"#
2053 );
2054 assert_eq!(
2055 field_hobbies.source_json_value(JSON),
2056 r#"["song writing", "thinking about society"]"#
2057 );
2058
2059 let hobbies_one = walk.next().unwrap();
2060 assert_eq!(hobbies_one.source_json(JSON), r#""song writing""#);
2061 assert_eq!(hobbies_one.source_json_value(JSON), r#""song writing""#);
2062
2063 let hobbies_two = walk.next().unwrap();
2064 assert_eq!(hobbies_two.source_json(JSON), r#""thinking about society""#);
2065 assert_eq!(
2066 hobbies_two.source_json_value(JSON),
2067 r#""thinking about society""#
2068 );
2069 }
2070}
2071
2072#[cfg(test)]
2073mod test_path_node {
2074 use std::rc::Rc;
2075
2076 use super::{
2077 parser::{RawStr, Token, TokenType},
2078 PathNode, Span,
2079 };
2080
2081 #[test]
2082 fn should_display_path() {
2083 let root = Rc::new(PathNode::Root);
2084 let path_a = Rc::new(PathNode::Array {
2085 parent: Rc::clone(&root),
2086 index: 1,
2087 });
2088 let path_b = Rc::new(PathNode::Object {
2089 parent: Rc::clone(&path_a),
2090 key: r#""name""#.into(),
2091 });
2092 let path_c = Rc::new(PathNode::Object {
2093 parent: Rc::clone(&path_b),
2094 key: r#""gene""#.into(),
2095 });
2096
2097 assert_eq!(*root, "$");
2098 assert_eq!(*path_a, "$.1");
2099 assert_eq!(*path_b, "$.1.name");
2100 assert_eq!(*path_c, "$.1.name.gene");
2101 }
2102
2103 impl<'buf> From<&'buf str> for RawStr<'buf> {
2104 #[track_caller]
2105 fn from(s: &'buf str) -> Self {
2106 RawStr::from_quoted_str(
2107 s,
2108 Token {
2109 kind: TokenType::String,
2110 span: Span::default(),
2111 },
2112 )
2113 .unwrap()
2114 }
2115 }
2116}