#[cfg(test)]
pub(crate) mod test;
#[cfg(test)]
mod test_path;
#[cfg(test)]
mod test_parse_with_schema;
#[cfg(test)]
mod test_source_json;
#[cfg(test)]
mod test_path_node;
#[cfg(test)]
mod test_path_node_matches_str;
#[cfg(test)]
mod test_path_matches_glob;
pub mod decode;
pub mod parser;
pub(crate) mod schema;
pub(crate) mod walk;
pub mod write;
use std::{
collections::BTreeMap,
fmt::{self, Write as _},
sync::Arc,
};
use tracing::{trace, Level};
use crate::{
warning::{self, IntoCaveat},
Verdict,
};
use parser::ErrorKind;
use parser::{Parser, Span};
#[doc(inline)]
pub use parser::{line_col, Error, ErrorReport, LineCol};
pub(crate) use parser::{parse, RawStr};
const PATH_SEPARATOR: char = '.';
const PATH_ROOT: &str = "$";
#[doc(hidden)]
#[macro_export]
macro_rules! required_field_or_bail {
($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {
match $fields.get($field_name) {
Some(field_elem) => field_elem,
None => {
return $warnings.bail(
Warning::FieldRequired {
field_name: $field_name.into(),
},
$elem,
);
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! required_field {
($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {{
let field = $fields.get($field_name);
if field.is_none() {
$warnings.insert(
Warning::FieldRequired {
field_name: $field_name.into(),
},
$elem,
);
}
field
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! expect_object_or_bail {
($elem:expr, $warnings:expr) => {
match $elem.as_object_fields() {
Some(fields) => fields,
None => {
return $warnings.bail(
Warning::FieldInvalidType {
expected_type: json::ValueKind::Object,
},
$elem,
);
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! expect_array_or_bail {
($elem:expr, $warnings:expr) => {
match $elem.as_array() {
Some(fields) => fields,
None => {
return $warnings.bail(
Warning::FieldInvalidType {
expected_type: json::ValueKind::Array,
},
$elem,
);
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! parse_required_or_bail {
($elem:expr, $fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
#[allow(
unused,
reason = "the macro uses the import but maybe the outside scope does too."
)]
use $crate::json::FromJson;
use $crate::warning::GatherWarnings as _;
let elem = $crate::required_field_or_bail!($elem, $fields, $elem_name, $warnings);
<$target as FromJson>::from_json(elem)?.gather_warnings_into(&mut $warnings)
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! parse_required {
($elem:expr, $fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
#[allow(
unused,
reason = "the macro uses the import but maybe the outside scope does too."
)]
use $crate::json::FromJson;
use $crate::warning::GatherWarnings as _;
let elem = $crate::required_field!($elem, $fields, $elem_name, $warnings);
if let Some(elem) = elem {
let value =
<$target as FromJson>::from_json(elem)?.gather_warnings_into(&mut $warnings);
Some(value)
} else {
None
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! parse_nullable_or_bail {
($fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
#[allow(
unused,
reason = "the macro uses the import but maybe the outside scope does too."
)]
use $crate::json::FromJson as _;
use $crate::warning::GatherWarnings as _;
match $fields.get($elem_name) {
Some(elem) => Option::<$target>::from_json(elem)?.gather_warnings_into(&mut $warnings),
None => None,
}
}};
}
pub(crate) trait FromJson<'buf>: Sized {
type Warning: crate::Warning;
fn from_json(elem: &Element<'buf>) -> Verdict<Self, Self::Warning>;
}
impl<'buf, T> FromJson<'buf> for Option<T>
where
T: FromJson<'buf> + IntoCaveat,
{
type Warning = T::Warning;
fn from_json(elem: &Element<'buf>) -> Verdict<Self, Self::Warning> {
let value = elem.as_value();
if value.is_null() {
Ok(None.into_caveat(warning::Set::new()))
} else {
let v = T::from_json(elem)?;
Ok(v.map(Some))
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Element<'buf> {
id: ElemId,
path_node: PathNodeRef<'buf>,
span: Span,
value: Value<'buf>,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub(crate) struct ElemId(usize);
impl fmt::Display for ElemId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl<'buf> Element<'buf> {
fn new(id: ElemId, path: PathNodeRef<'buf>, span: Span, value: Value<'buf>) -> Element<'buf> {
Element {
id,
path_node: path,
span,
value,
}
}
pub(crate) const fn id(&self) -> ElemId {
self.id
}
pub fn path(&self) -> PathRef<'buf> {
PathRef(self.path_node())
}
pub(crate) fn path_node(&self) -> PathNodeRef<'buf> {
Arc::clone(&self.path_node)
}
pub fn span(&self) -> Span {
self.span
}
pub fn source_json(&self, source_json: &'buf str) -> SourceStr<'buf> {
if let PathNode::Object { key, .. } = *self.path_node {
let span = Span {
start: key.span().start,
end: self.span.end,
};
let field_str = &source_json
.get(span.start..span.end)
.expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR");
let field = RawStr::from_str(field_str, span);
let (key, value) = field_str
.split_once(':')
.expect("An objects field always contains a delimiting `:`");
SourceStr::Field { field, key, value }
} else {
let span = self.span;
let s = source_json
.get(span.start..span.end)
.expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR");
SourceStr::Value(RawStr::from_str(s, span))
}
}
pub fn source_json_value(&self, source_json: &'buf str) -> &'buf str {
source_json
.get(self.span.start..self.span.end)
.expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR")
}
pub(crate) fn value(&self) -> &Value<'buf> {
&self.value
}
pub(crate) fn as_value(&self) -> &Value<'buf> {
&self.value
}
pub(crate) fn to_raw_str(&self) -> Option<RawStr<'buf>> {
self.value.to_raw_str()
}
pub(crate) fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
self.value.as_object_fields()
}
pub(crate) fn as_array(&self) -> Option<&[Element<'buf>]> {
self.value.as_array()
}
pub fn as_number_str(&self) -> Option<&str> {
self.value.as_number()
}
pub fn is_null(&self) -> bool {
self.value.is_null()
}
}
#[derive(Debug)]
pub enum SourceStr<'buf> {
Value(RawStr<'buf>),
Field {
field: RawStr<'buf>,
key: &'buf str,
value: &'buf str,
},
}
impl fmt::Display for SourceStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SourceStr::Value(s) => f.write_str(s.as_raw()),
SourceStr::Field { field, .. } => f.write_str(field.as_raw()),
}
}
}
impl PartialEq<&str> for SourceStr<'_> {
fn eq(&self, other: &&str) -> bool {
match self {
SourceStr::Value(s) => s.as_raw() == *other,
SourceStr::Field { field, .. } => field.as_raw() == *other,
}
}
}
impl PartialEq<String> for SourceStr<'_> {
fn eq(&self, other: &String) -> bool {
self.eq(&&**other)
}
}
impl PartialOrd for Element<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Element<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.path_node.cmp(&other.path_node)
}
}
impl fmt::Display for Element<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = {}", self.path_node, self.value)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Field<'buf>(Element<'buf>);
impl<'buf> Field<'buf> {
#[expect(
clippy::unreachable,
reason = "A Field is created by the parser when the type is an Object."
)]
pub(crate) fn key(&self) -> RawStr<'buf> {
let PathNode::Object { key, .. } = *self.0.path_node else {
unreachable!();
};
key
}
pub(crate) fn into_element(self) -> Element<'buf> {
self.0
}
pub(crate) fn element(&self) -> &Element<'buf> {
&self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Value<'buf> {
Null,
True,
False,
String(RawStr<'buf>),
Number(&'buf str),
Array(Vec<Element<'buf>>),
Object(Vec<Field<'buf>>),
}
impl<'buf> Value<'buf> {
pub(crate) fn kind(&self) -> ValueKind {
match self {
Value::Null => ValueKind::Null,
Value::True | Value::False => ValueKind::Bool,
Value::String(_) => ValueKind::String,
Value::Number(_) => ValueKind::Number,
Value::Array(_) => ValueKind::Array,
Value::Object(_) => ValueKind::Object,
}
}
pub(crate) fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub(crate) fn is_scalar(&self) -> bool {
matches!(
self,
Value::Null | Value::True | Value::False | Value::String(_) | Value::Number(_)
)
}
pub(crate) fn as_array(&self) -> Option<&[Element<'buf>]> {
match self {
Value::Array(elems) => Some(elems),
_ => None,
}
}
pub(crate) fn as_number(&self) -> Option<&str> {
match self {
Value::Number(s) => Some(s),
_ => None,
}
}
pub(crate) fn to_raw_str(&self) -> Option<RawStr<'buf>> {
match self {
Value::String(s) => Some(*s),
_ => None,
}
}
pub(crate) fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
match self {
Value::Object(fields) => Some(fields),
_ => None,
}
}
}
impl fmt::Display for Value<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => write!(f, "null"),
Self::True => write!(f, "true"),
Self::False => write!(f, "false"),
Self::String(s) => write!(f, "{s}"),
Self::Number(s) => write!(f, "{s}"),
Self::Array(..) => f.write_str("[...]"),
Self::Object(..) => f.write_str("{...}"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ValueKind {
Null,
Bool,
Number,
String,
Array,
Object,
}
impl fmt::Display for ValueKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueKind::Null => write!(f, "null"),
ValueKind::Bool => write!(f, "bool"),
ValueKind::Number => write!(f, "number"),
ValueKind::String => write!(f, "string"),
ValueKind::Array => write!(f, "array"),
ValueKind::Object => write!(f, "object"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ObjectKind {
Object,
Array,
}
pub type RawMap<'buf> = BTreeMap<RawStr<'buf>, Element<'buf>>;
pub type RawRefMap<'a, 'buf> = BTreeMap<RawStr<'buf>, &'a Element<'buf>>;
#[expect(dead_code, reason = "pending use in `tariff::lint`")]
pub(crate) trait FieldsIntoExt<'buf> {
fn into_map(self) -> RawMap<'buf>;
}
pub(crate) trait FieldsAsExt<'buf> {
fn as_raw_map(&self) -> RawRefMap<'_, 'buf>;
fn find_field(&self, key: &str) -> Option<&Field<'buf>>;
}
impl<'buf> FieldsIntoExt<'buf> for Vec<Field<'buf>> {
fn into_map(self) -> RawMap<'buf> {
self.into_iter()
.map(|field| (field.key(), field.into_element()))
.collect()
}
}
impl<'buf> FieldsAsExt<'buf> for Vec<Field<'buf>> {
fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
self.iter()
.map(|field| (field.key(), field.element()))
.collect()
}
fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
self.iter().find(|field| field.key().as_raw() == key)
}
}
impl<'buf> FieldsAsExt<'buf> for [Field<'buf>] {
fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
self.iter()
.map(|field| (field.key(), field.element()))
.collect()
}
fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
self.iter().find(|field| field.key().as_raw() == key)
}
}
#[derive(Clone, Debug)]
pub struct UnexpectedFields<'buf>(Vec<PathNodeRef<'buf>>);
impl fmt::Display for UnexpectedFields<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.write_str("[\n")?;
for entry in &self.0 {
writeln!(f, "\t\"{entry}\",")?;
}
f.write_str("]\n")?;
} else {
f.write_char('[')?;
for entry in &self.0 {
write!(f, "{entry},")?;
}
f.write_char(']')?;
}
Ok(())
}
}
impl<'buf> UnexpectedFields<'buf> {
pub(crate) fn empty() -> Self {
Self(vec![])
}
pub(crate) fn from_vec(v: Vec<PathNodeRef<'buf>>) -> Self {
Self(v)
}
pub fn to_strings(&self) -> Vec<String> {
self.0.iter().map(ToString::to_string).collect()
}
pub fn into_strings(self) -> Vec<String> {
self.0.into_iter().map(|path| path.to_string()).collect()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter<'a>(&'a self) -> UnexpectedFieldsIter<'a, 'buf> {
UnexpectedFieldsIter(self.0.iter())
}
}
impl<'buf> IntoIterator for UnexpectedFields<'buf> {
type Item = PathRef<'buf>;
type IntoIter = UnexpectedFieldsIntoIter<'buf>;
fn into_iter(self) -> Self::IntoIter {
UnexpectedFieldsIntoIter(self.0.into_iter())
}
}
pub struct UnexpectedFieldsIntoIter<'buf>(std::vec::IntoIter<PathNodeRef<'buf>>);
impl<'buf> Iterator for UnexpectedFieldsIntoIter<'buf> {
type Item = PathRef<'buf>;
fn next(&mut self) -> Option<Self::Item> {
let path_node = self.0.next()?;
Some(PathRef(path_node))
}
}
impl<'a, 'buf> IntoIterator for &'a UnexpectedFields<'buf> {
type Item = PathRef<'buf>;
type IntoIter = UnexpectedFieldsIter<'a, 'buf>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct UnexpectedFieldsIter<'a, 'buf>(std::slice::Iter<'a, PathNodeRef<'buf>>);
impl<'buf> Iterator for UnexpectedFieldsIter<'_, 'buf> {
type Item = PathRef<'buf>;
fn next(&mut self) -> Option<Self::Item> {
let path_node = self.0.next()?;
Some(PathRef(Arc::clone(path_node)))
}
}
pub struct PathRef<'buf>(PathNodeRef<'buf>);
impl fmt::Debug for PathRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl<'buf> PathRef<'buf> {
pub fn components(&self) -> PathComponents<'buf> {
PathComponents(PathIter::new(Arc::clone(&self.0)))
}
}
pub struct PathComponents<'buf>(PathIter<'buf>);
impl<'buf> Iterator for PathComponents<'buf> {
type Item = PathComponent<'buf>;
fn next(&mut self) -> Option<Self::Item> {
let path_node = self.0.next()?;
Some(PathComponent(path_node))
}
}
impl PartialEq<&str> for PathRef<'_> {
fn eq(&self, other: &&str) -> bool {
match_path_node(&self.0, other, |_| false)
}
}
impl PartialEq<String> for PathRef<'_> {
fn eq(&self, other: &String) -> bool {
match_path_node(&self.0, other, |_| false)
}
}
impl fmt::Display for PathRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
pub(crate) type PathNodeRef<'buf> = Arc<PathNode<'buf>>;
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum PathNode<'buf> {
#[default]
Root,
Array {
parent: PathNodeRef<'buf>,
index: usize,
},
Object {
parent: PathNodeRef<'buf>,
key: RawStr<'buf>,
},
}
pub enum PathNodeKind {
Root,
Array,
Object,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Path(Vec<PathPiece>);
impl Path {
const fn root() -> Self {
Self(vec![])
}
fn from_node(path: PathNodeRef<'_>) -> Self {
let paths: Vec<_> = PathIter::new(path).collect();
let pieces = paths
.into_iter()
.rev()
.filter_map(|path_node| match *path_node {
PathNode::Root => None,
PathNode::Array { index, .. } => Some(PathPiece::Array(index)),
PathNode::Object { key, .. } => Some(PathPiece::Object(key.to_string())),
})
.collect();
Self(pieces)
}
}
impl PartialEq<&str> for Path {
fn eq(&self, other: &&str) -> bool {
match_path(self, other)
}
}
impl PartialEq<String> for Path {
fn eq(&self, other: &String) -> bool {
match_path(self, other)
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let iter = self.0.iter();
write!(f, "$")?;
for path in iter {
write!(f, ".{path}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
enum PathPiece {
Array(usize),
Object(String),
}
impl fmt::Display for PathPiece {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathPiece::Array(index) => write!(f, "{index}"),
PathPiece::Object(key) => write!(f, "{key}"),
}
}
}
pub struct PathComponent<'buf>(PathNodeRef<'buf>);
impl PathComponent<'_> {
pub fn kind(&self) -> PathNodeKind {
match *self.0 {
PathNode::Root => PathNodeKind::Root,
PathNode::Array { .. } => PathNodeKind::Array,
PathNode::Object { .. } => PathNodeKind::Object,
}
}
}
impl fmt::Display for PathComponent<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self.0 {
PathNode::Root => f.write_str(PATH_ROOT),
PathNode::Array { index, .. } => write!(f, "{index}"),
PathNode::Object { key, .. } => write!(f, "{key}"),
}
}
}
impl fmt::Display for PathNode<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let paths: Vec<_> = PathIter::new(Arc::new(self.clone())).collect();
let mut iter = paths.into_iter().rev();
if f.alternate() {
for path in iter {
match *path {
PathNode::Root => f.write_str("")?,
PathNode::Array { .. } | PathNode::Object { .. } => f.write_str("...|")?,
}
}
} else {
if let Some(path) = iter.next() {
write!(f, "{}", PathComponent(path))?;
}
for path in iter {
write!(f, ".{}", PathComponent(path))?;
}
}
Ok(())
}
}
impl<'buf> PathNode<'buf> {
pub(crate) fn is_root(&self) -> bool {
matches!(self, PathNode::Root)
}
pub(crate) fn is_array(&self) -> bool {
matches!(self, PathNode::Array { .. })
}
pub(crate) fn as_object_key(&self) -> Option<&RawStr<'buf>> {
match self {
PathNode::Object { key, .. } => Some(key),
PathNode::Root | PathNode::Array { .. } => None,
}
}
}
fn match_path_node<F>(path: &PathNode<'_>, s: &str, mut skip: F) -> bool
where
F: FnMut(&str) -> bool,
{
let mut parts = s.rsplit(PATH_SEPARATOR);
let mut paths_iter = PathIter::new(Arc::new(path.clone()));
loop {
let node_segment = paths_iter.next();
let str_segment = parts.next();
let (node_segment, str_segment) = match (node_segment, str_segment) {
(None, None) => return true,
(None, Some(_)) | (Some(_), None) => return false,
(Some(a), Some(b)) => (a, b),
};
if skip(str_segment) {
continue;
}
let yip = match *node_segment {
PathNode::Root => str_segment == PATH_ROOT,
PathNode::Array { index, .. } => {
let Ok(b) = str_segment.parse::<usize>() else {
return false;
};
index == b
}
PathNode::Object { key, .. } => key.as_raw() == str_segment,
};
if !yip {
return false;
}
}
}
fn match_path(path: &Path, s: &str) -> bool {
let mut parts = s.split(PATH_SEPARATOR);
let mut paths_iter = path.0.iter();
let Some(str_segment) = parts.next() else {
return false;
};
if str_segment != PATH_ROOT {
return false;
}
loop {
let node_segment = paths_iter.next();
let str_segment = parts.next();
let (node_segment, str_segment) = match (node_segment, str_segment) {
(None, None) => return true,
(None, Some(_)) | (Some(_), None) => return false,
(Some(a), Some(b)) => (a, b),
};
let yip = match node_segment {
PathPiece::Array(index) => {
let Ok(b) = str_segment.parse::<usize>() else {
return false;
};
*index == b
}
PathPiece::Object(key) => key == str_segment,
};
if !yip {
return false;
}
}
}
impl PartialEq<&str> for PathNode<'_> {
fn eq(&self, other: &&str) -> bool {
match_path_node(self, other, |_| false)
}
}
impl PartialEq<String> for PathNode<'_> {
fn eq(&self, other: &String) -> bool {
match_path_node(self, other, |_| false)
}
}
struct PathIter<'buf> {
complete: bool,
path: PathNodeRef<'buf>,
}
impl<'buf> PathIter<'buf> {
fn new(path: PathNodeRef<'buf>) -> Self {
Self {
complete: false,
path,
}
}
}
impl<'buf> Iterator for PathIter<'buf> {
type Item = PathNodeRef<'buf>;
fn next(&mut self) -> Option<Self::Item> {
if self.complete {
return None;
}
match &*self.path {
PathNode::Root => {
self.complete = true;
Some(Arc::clone(&self.path))
}
PathNode::Array { parent, .. } | PathNode::Object { parent, .. } => {
let next = Arc::clone(&self.path);
self.path = Arc::clone(parent);
Some(next)
}
}
}
}
struct DisplayExpectStack<'a>(&'a [schema::Expect]);
impl fmt::Display for DisplayExpectStack<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut iter = self.0.iter().rev();
let last = iter.next();
f.write_str("~")?;
for _ in iter {
f.write_str("...~")?;
}
if let Some(exp) = last {
match exp {
schema::Expect::Scalar => f.write_str("~")?,
schema::Expect::Array(element) => match &**element {
schema::Element::Scalar => f.write_str("~")?,
schema::Element::Array(element) => write!(f, "[{element:?}]")?,
schema::Element::Object(fields) => {
write!(f, "[{{{:}}}])", DisplayExpectFields(&**fields))?;
}
},
schema::Expect::Object(fields) => {
write!(f, "{{{:}}}", DisplayExpectFields(&**fields))?;
}
schema::Expect::UnmatchedScalar => write!(f, "unmatched(scalar)")?,
schema::Expect::UnmatchedArray => write!(f, "unmatched(array)")?,
schema::Expect::UnmatchedObject => write!(f, "unmatched(object)")?,
schema::Expect::OutOfSchema => write!(f, "no_schema")?,
}
}
Ok(())
}
}
struct DisplayExpectFields<'a, V>(&'a BTreeMap<&'a str, V>);
impl<V> fmt::Display for DisplayExpectFields<'_, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const MAX_FIELDS: usize = 8;
let mut count = 0;
let mut iter = self.0.keys().peekable();
loop {
if count >= MAX_FIELDS {
f.write_str("...")?;
break;
}
let Some(field) = iter.next() else {
break;
};
let Some(n) = count.checked_add(1) else {
break;
};
count = n;
write!(f, "{field}")?;
let Some(_) = iter.peek() else {
break;
};
f.write_str(", ")?;
}
Ok(())
}
}
#[derive(Debug)]
struct UnbalancedExpectStack;
impl fmt::Display for UnbalancedExpectStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unbalanced expectation stack")
}
}
impl std::error::Error for UnbalancedExpectStack {}
pub(crate) fn parse_with_schema<'buf>(
json: &'buf str,
schema: &schema::Element,
) -> Result<ParseReport<'buf>, Error> {
let parser = Parser::new(json);
let mut unexpected_fields = vec![];
let mut expectation_stack = vec![schema.to_expectation()];
for event in parser {
match event? {
parser::Event::Open { kind, parent_path } => {
let Some(expectation) = expectation_stack.pop() else {
return Err(ErrorKind::Internal(Box::new(UnbalancedExpectStack))
.into_partial_error_without_token()
.with_root_path());
};
if tracing::enabled!(Level::DEBUG) {
match kind {
ObjectKind::Array => {
trace!("{parent_path} [ {}", DisplayExpectStack(&expectation_stack));
}
ObjectKind::Object => trace!(
"{parent_path} {{ {}",
DisplayExpectStack(&expectation_stack)
),
}
}
match expectation {
schema::Expect::Array(elem) => {
if parent_path.is_root() {
let next = match kind {
ObjectKind::Array => schema::Expect::Array(elem),
ObjectKind::Object => schema::Expect::UnmatchedArray,
};
expectation_stack.push(next);
trace!("{}", DisplayExpectStack(&expectation_stack));
continue;
}
if !parent_path.is_array() {
expectation_stack.push(schema::Expect::UnmatchedArray);
trace!("{}", DisplayExpectStack(&expectation_stack));
continue;
}
expectation_stack.push(schema::Expect::Array(Arc::clone(&elem)));
expectation_stack.push(elem.to_expectation());
}
schema::Expect::Object(fields) => {
if parent_path.is_root() {
let next = match kind {
ObjectKind::Array => schema::Expect::UnmatchedObject,
ObjectKind::Object => schema::Expect::Object(fields),
};
expectation_stack.push(next);
trace!("{}", DisplayExpectStack(&expectation_stack));
continue;
}
let Some(key) = parent_path.as_object_key() else {
expectation_stack.push(schema::Expect::UnmatchedObject);
trace!("{}", DisplayExpectStack(&expectation_stack));
continue;
};
let next = if let Some(elem) = fields.get(key.as_raw()) {
open_object(kind, elem.as_ref())
} else {
unexpected_fields.push(parent_path);
schema::Expect::OutOfSchema
};
expectation_stack.push(schema::Expect::Object(fields));
expectation_stack.push(next);
}
schema::Expect::OutOfSchema => {
expectation_stack.push(expectation);
expectation_stack.push(schema::Expect::OutOfSchema);
}
schema::Expect::UnmatchedArray | schema::Expect::UnmatchedObject => {
expectation_stack.push(expectation);
expectation_stack.push(schema::Expect::OutOfSchema);
}
_ => {
expectation_stack.push(expectation);
}
}
trace!("{}", DisplayExpectStack(&expectation_stack));
}
parser::Event::Element { kind, parent_path } => {
let Some(expectation) = expectation_stack.pop() else {
return Err(ErrorKind::Internal(Box::new(UnbalancedExpectStack))
.into_partial_error_without_token()
.with_root_path());
};
if let ValueKind::Array | ValueKind::Object = kind {
if tracing::enabled!(Level::DEBUG) {
match kind {
ValueKind::Array => {
trace!(
"{parent_path} ] {}",
DisplayExpectStack(&expectation_stack)
);
}
ValueKind::Object => trace!(
"{parent_path} }} {}",
DisplayExpectStack(&expectation_stack)
),
_ => (),
}
}
continue;
}
match expectation {
#[expect(
clippy::unreachable,
reason = "The parser only emits an `Event::Complete` for a scalar object at the root"
)]
schema::Expect::Object(fields) => match &*parent_path {
PathNode::Root => unreachable!(),
PathNode::Array { .. } => {
expectation_stack.push(schema::Expect::UnmatchedObject);
}
PathNode::Object { parent, key } => {
trace!("{parent:#}.{key}");
if !fields.contains_key(key.as_raw()) {
unexpected_fields.push(parent_path);
}
expectation_stack.push(schema::Expect::Object(fields));
}
},
schema::Expect::OutOfSchema => {
unexpected_fields.push(parent_path);
expectation_stack.push(expectation);
}
_ => {
expectation_stack.push(expectation);
}
}
}
parser::Event::Complete(element) => {
if element.value().is_scalar() {
unexpected_fields.push(element.path_node());
}
return Ok(ParseReport {
element,
unexpected_fields: UnexpectedFields::from_vec(unexpected_fields),
});
}
}
}
Err(ErrorKind::UnexpectedEOF
.into_partial_error_without_token()
.with_root_path())
}
fn open_object(kind: ObjectKind, elem: Option<&Arc<schema::Element>>) -> schema::Expect {
let Some(schema) = elem else {
return schema::Expect::OutOfSchema;
};
match (kind, &**schema) {
(ObjectKind::Object | ObjectKind::Array, schema::Element::Scalar) => {
schema::Expect::UnmatchedScalar
}
(ObjectKind::Object, schema::Element::Array(_)) => schema::Expect::UnmatchedArray,
(ObjectKind::Object, schema::Element::Object(fields)) => {
schema::Expect::Object(Arc::clone(fields))
}
(ObjectKind::Array, schema::Element::Array(element)) => {
schema::Expect::Array(Arc::clone(element))
}
(ObjectKind::Array, schema::Element::Object(_)) => schema::Expect::UnmatchedObject,
}
}
#[derive(Debug)]
pub(crate) struct ParseReport<'buf> {
pub element: Element<'buf>,
pub unexpected_fields: UnexpectedFields<'buf>,
}