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