pub mod json_path {
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum JsonPathPiece {
ArrayItem(u32),
ObjectMember(String),
}
impl From<u32> for JsonPathPiece {
fn from(v: u32) -> Self {
JsonPathPiece::ArrayItem(v)
}
}
impl From<String> for JsonPathPiece {
fn from(v: String) -> Self {
JsonPathPiece::ObjectMember(v)
}
}
impl From<&str> for JsonPathPiece {
fn from(v: &str) -> Self {
JsonPathPiece::ObjectMember(v.to_string())
}
}
pub type JsonPath = [JsonPathPiece];
pub(crate) fn format_abs_json_path(json_path: &JsonPath) -> String {
"$".to_string()
+ json_path
.iter()
.map(|p| match p {
JsonPathPiece::ArrayItem(index) => format!("[{index}]"),
JsonPathPiece::ObjectMember(name) => format!(".{name}"),
})
.collect::<String>()
.as_str()
}
#[macro_export]
macro_rules! json_path {
() => {
[] as [$crate::reader::json_path::JsonPathPiece; 0]
};
( $($piece:expr),+ $(,)? ) => {
[
$(
$crate::reader::json_path::JsonPathPiece::from($piece),
)*
]
};
}
#[doc(inline)]
pub use json_path;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_abs_json_path() {
assert_eq!("$", format_abs_json_path(&Vec::new()));
assert_eq!("$[2]", format_abs_json_path(&[JsonPathPiece::ArrayItem(2)]));
assert_eq!(
"$[2][3]",
format_abs_json_path(&[JsonPathPiece::ArrayItem(2), JsonPathPiece::ArrayItem(3)])
);
assert_eq!(
"$[2].a",
format_abs_json_path(&[
JsonPathPiece::ArrayItem(2),
JsonPathPiece::ObjectMember("a".to_owned())
])
);
assert_eq!(
"$.a",
format_abs_json_path(&[JsonPathPiece::ObjectMember("a".to_owned())])
);
assert_eq!(
"$.a.b",
format_abs_json_path(&[
JsonPathPiece::ObjectMember("a".to_owned()),
JsonPathPiece::ObjectMember("b".to_owned())
])
);
assert_eq!(
"$.a[2]",
format_abs_json_path(&[
JsonPathPiece::ObjectMember("a".to_owned()),
JsonPathPiece::ArrayItem(2)
])
);
}
#[test]
fn macro_json_path() {
assert_eq!(json_path![], []);
assert_ne!(json_path![], &[JsonPathPiece::ArrayItem(1)] as &JsonPath);
assert_eq!(json_path![3], [JsonPathPiece::ArrayItem(3)]);
assert_eq!(json_path![3], &[JsonPathPiece::ArrayItem(3)] as &JsonPath);
assert_eq!(
json_path!["a"],
[JsonPathPiece::ObjectMember("a".to_owned())]
);
assert_eq!(
json_path!["a",],
[JsonPathPiece::ObjectMember("a".to_owned())]
);
assert_eq!(
json_path!["a".to_owned()],
[JsonPathPiece::ObjectMember("a".to_owned())]
);
assert_eq!(
json_path!["outer", 1, "inner".to_owned(), 2],
[
JsonPathPiece::ObjectMember("outer".to_owned()),
JsonPathPiece::ArrayItem(1),
JsonPathPiece::ObjectMember("inner".to_owned()),
JsonPathPiece::ArrayItem(2),
]
);
}
}
}
use std::{
fmt::{Debug, Display, Formatter},
io::Read,
str::FromStr,
};
use thiserror::Error;
use self::json_path::{JsonPath, JsonPathPiece, format_abs_json_path};
use crate::writer::JsonWriter;
mod stream_reader;
pub use stream_reader::*;
#[cfg(feature = "simple-api")]
pub mod simple;
type IoError = std::io::Error;
#[derive(PartialEq, Eq, Clone, Copy, strum::Display, Debug)]
pub enum ValueType {
Array,
Object,
String,
Number,
Boolean,
Null,
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub struct LinePosition {
pub line: u64,
pub column: u64,
}
impl Display for LinePosition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "line {}, column {}", self.line, self.column)
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct JsonReaderPosition {
pub path: Option<Vec<JsonPathPiece>>,
pub line_pos: Option<LinePosition>,
pub data_pos: Option<u64>,
}
impl JsonReaderPosition {
#[allow(
dead_code,
reason = "called by Simple API (but guarded by feature flag)"
)]
pub(crate) fn unknown_position() -> Self {
JsonReaderPosition {
path: None,
line_pos: None,
data_pos: None,
}
}
}
impl Display for JsonReaderPosition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(path) = &self.path {
write!(f, "path '{}'", format_abs_json_path(path))?;
if let Some(line_pos) = &self.line_pos {
write!(f, ", {line_pos}")?;
if let Some(data_pos) = &self.data_pos {
write!(f, " (data pos {data_pos})")?;
}
} else if let Some(data_pos) = &self.data_pos {
write!(f, ", data pos {data_pos}")?;
}
} else if let Some(line_pos) = &self.line_pos {
write!(f, "{line_pos}")?;
if let Some(data_pos) = &self.data_pos {
write!(f, " (data pos {data_pos})")?;
}
} else if let Some(data_pos) = &self.data_pos {
write!(f, "data pos {data_pos}")?;
} else {
write!(f, "<location unavailable>")?;
}
Ok(())
}
}
#[derive(Error, PartialEq, Eq, Clone, Debug)]
#[error("JSON syntax error {kind} at {location}")]
pub struct JsonSyntaxError {
pub kind: SyntaxErrorKind,
pub location: JsonReaderPosition,
}
#[non_exhaustive]
#[derive(PartialEq, Eq, Clone, Copy, strum::Display, Debug)]
pub enum SyntaxErrorKind {
CommentsNotEnabled,
IncompleteComment,
BlockCommentNotClosed,
InvalidLiteral,
TrailingDataAfterLiteral,
UnexpectedClosingBracket,
UnexpectedComma,
MissingComma,
TrailingCommaNotEnabled,
UnexpectedColon,
MissingColon,
MalformedNumber,
TrailingDataAfterNumber,
ExpectingMemberNameOrObjectEnd,
MalformedJson,
NotEscapedControlCharacter,
UnknownEscapeSequence,
MalformedEscapeSequence,
UnpairedSurrogatePairEscapeSequence,
IncompleteDocument,
TrailingData,
}
#[derive(PartialEq, Eq, Clone, strum::Display, Debug)]
pub enum UnexpectedStructureKind {
#[strum(to_string = "TooShortArray(expected_index = {expected_index})")]
TooShortArray {
expected_index: u32,
},
#[strum(to_string = "MissingObjectMember(\"{member_name}\")")]
MissingObjectMember {
member_name: String,
},
FewerElementsThanExpected,
MoreElementsThanExpected,
}
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ReaderError {
#[error("syntax error: {0}")]
SyntaxError(#[from] JsonSyntaxError),
#[error("expected JSON value type {expected} but got {actual} at {location}")]
UnexpectedValueType {
expected: ValueType,
actual: ValueType,
location: JsonReaderPosition,
},
#[error("unexpected JSON structure {kind} at {location}")]
UnexpectedStructure {
kind: UnexpectedStructureKind,
location: JsonReaderPosition,
},
#[error("maximum nesting depth {max_nesting_depth} exceeded at {location}")]
MaxNestingDepthExceeded {
max_nesting_depth: u32,
location: JsonReaderPosition,
},
#[error("unsupported number value '{number}' at {location}")]
UnsupportedNumberValue {
number: String,
location: JsonReaderPosition,
},
#[error("IO error '{error}' at (roughly) {location}")]
IoError {
error: IoError,
location: JsonReaderPosition,
},
}
impl ReaderError {
#[allow(
dead_code,
reason = "called by Simple API (but guarded by feature flag)"
)]
pub(crate) fn rough_clone(&self) -> Self {
match self {
Self::SyntaxError(e) => Self::SyntaxError(e.clone()),
Self::UnexpectedValueType {
expected,
actual,
location,
} => Self::UnexpectedValueType {
expected: *expected,
actual: *actual,
location: location.clone(),
},
Self::UnexpectedStructure { kind, location } => Self::UnexpectedStructure {
kind: kind.clone(),
location: location.clone(),
},
Self::MaxNestingDepthExceeded {
max_nesting_depth,
location,
} => Self::MaxNestingDepthExceeded {
max_nesting_depth: *max_nesting_depth,
location: location.clone(),
},
Self::UnsupportedNumberValue { number, location } => Self::UnsupportedNumberValue {
number: number.clone(),
location: location.clone(),
},
Self::IoError { error, location } => Self::IoError {
error: IoError::new(error.kind(), error.to_string()),
location: location.clone(),
},
}
}
}
#[derive(Error, Debug)]
pub enum TransferError {
#[error("reader error: {0}")]
ReaderError(#[from] ReaderError),
#[error("writer error: {0}")]
WriterError(#[from] IoError),
}
pub trait JsonReader {
fn peek(&mut self) -> Result<ValueType, ReaderError>;
fn begin_object(&mut self) -> Result<(), ReaderError>;
fn end_object(&mut self) -> Result<(), ReaderError>;
fn begin_array(&mut self) -> Result<(), ReaderError>;
fn end_array(&mut self) -> Result<(), ReaderError>;
fn has_next(&mut self) -> Result<bool, ReaderError>;
fn next_name(&mut self) -> Result<&str, ReaderError>;
fn next_name_owned(&mut self) -> Result<String, ReaderError>;
fn next_str(&mut self) -> Result<&str, ReaderError>;
fn next_string(&mut self) -> Result<String, ReaderError>;
fn next_string_reader(&mut self) -> Result<impl Read + '_, ReaderError>;
fn next_number<T: FromStr>(&mut self) -> Result<Result<T, T::Err>, ReaderError> {
Ok(T::from_str(self.next_number_as_str()?))
}
fn next_number_as_str(&mut self) -> Result<&str, ReaderError>;
fn next_number_as_string(&mut self) -> Result<String, ReaderError>;
fn next_bool(&mut self) -> Result<bool, ReaderError>;
fn next_null(&mut self) -> Result<(), ReaderError>;
#[cfg(feature = "serde")]
fn deserialize_next<'de, D: serde_core::de::Deserialize<'de>>(
&mut self,
) -> Result<D, crate::serde::DeserializerError>;
fn skip_name(&mut self) -> Result<(), ReaderError>;
fn skip_value(&mut self) -> Result<(), ReaderError>;
fn seek_to(&mut self, rel_json_path: &JsonPath) -> Result<(), ReaderError> {
self.peek()?;
for path_piece in rel_json_path {
match path_piece {
JsonPathPiece::ArrayItem(index) => {
self.begin_array()?;
for i in 0..=*index {
if !self.has_next()? {
return Err(ReaderError::UnexpectedStructure {
kind: UnexpectedStructureKind::TooShortArray {
expected_index: *index,
},
location: self.current_position(true),
});
}
if i < *index {
self.skip_value()?;
}
}
}
JsonPathPiece::ObjectMember(name) => {
self.begin_object()?;
let mut found_member = false;
while self.has_next()? {
if self.next_name()? == name {
found_member = true;
break;
} else {
self.skip_value()?;
}
}
if !found_member {
return Err(ReaderError::UnexpectedStructure {
kind: UnexpectedStructureKind::MissingObjectMember {
member_name: name.clone(),
},
location: self.current_position(true),
});
}
}
}
}
Ok(())
}
fn seek_back(&mut self, rel_json_path: &JsonPath) -> Result<(), ReaderError> {
for piece in rel_json_path.iter().rev() {
match piece {
JsonPathPiece::ArrayItem(_) => {
while self.has_next()? {
self.skip_value()?;
}
self.end_array()?;
}
JsonPathPiece::ObjectMember(_) => {
while self.has_next()? {
self.skip_name()?;
self.skip_value()?;
}
self.end_object()?;
}
}
}
Ok(())
}
fn skip_to_top_level(&mut self) -> Result<(), ReaderError>;
fn transfer_to<W: JsonWriter>(&mut self, json_writer: &mut W) -> Result<(), TransferError>;
fn consume_trailing_whitespace(self) -> Result<(), ReaderError>;
fn current_position(&self, include_path: bool) -> JsonReaderPosition;
}
#[cfg(test)]
mod tests {
use std::io::ErrorKind;
use super::{
IoError, JsonReaderPosition, JsonSyntaxError, LinePosition, ReaderError, SyntaxErrorKind,
UnexpectedStructureKind, ValueType,
json_path::{JsonPathPiece, json_path},
};
#[test]
fn reader_error_clone() {
let original_location = JsonReaderPosition {
path: Some(json_path!["a", 1].to_vec()),
line_pos: Some(LinePosition { line: 0, column: 7 }),
data_pos: Some(7),
};
let syntax_error = JsonSyntaxError {
kind: SyntaxErrorKind::MalformedJson,
location: original_location.clone(),
};
let original = ReaderError::SyntaxError(syntax_error.clone());
match original.rough_clone() {
ReaderError::SyntaxError(e) => assert_eq!(syntax_error, e),
e => panic!("unexpected error: {e:?}"),
}
let original = ReaderError::UnexpectedValueType {
expected: ValueType::Array,
actual: ValueType::Boolean,
location: original_location.clone(),
};
match original.rough_clone() {
ReaderError::UnexpectedValueType {
expected: ValueType::Array,
actual: ValueType::Boolean,
location,
} => assert_eq!(original_location, location),
e => panic!("unexpected error: {e:?}"),
}
let unexpected_structure = UnexpectedStructureKind::TooShortArray { expected_index: 2 };
let original = ReaderError::UnexpectedStructure {
kind: unexpected_structure.clone(),
location: original_location.clone(),
};
match original.rough_clone() {
ReaderError::UnexpectedStructure { kind, location } => {
assert_eq!(unexpected_structure, kind);
assert_eq!(original_location, location);
}
e => panic!("unexpected error: {e:?}"),
}
let original = ReaderError::MaxNestingDepthExceeded {
max_nesting_depth: 5,
location: original_location.clone(),
};
match original.rough_clone() {
ReaderError::MaxNestingDepthExceeded {
max_nesting_depth: 5,
location,
} => assert_eq!(original_location, location),
e => panic!("unexpected error: {e:?}"),
}
let original = ReaderError::UnsupportedNumberValue {
number: "1e123456".to_owned(),
location: original_location.clone(),
};
match original.rough_clone() {
ReaderError::UnsupportedNumberValue { number, location } => {
assert_eq!("1e123456", number);
assert_eq!(original_location, location);
}
e => panic!("unexpected error: {e:?}"),
}
let original = ReaderError::IoError {
error: IoError::new(ErrorKind::InvalidData, "custom-message"),
location: original_location.clone(),
};
match original.rough_clone() {
ReaderError::IoError { error, location } => {
assert_eq!(ErrorKind::InvalidData, error.kind());
assert_eq!("custom-message", error.get_ref().unwrap().to_string());
assert_eq!(original_location, location);
}
e => panic!("unexpected error: {e:?}"),
}
}
#[test]
fn json_reader_location_display() {
let path = Some(vec![
JsonPathPiece::ArrayItem(1),
JsonPathPiece::ObjectMember("name".to_owned()),
]);
let line_pos = Some(LinePosition { line: 1, column: 2 });
let data_pos = Some(3);
let combinations = vec![
((None, None, None), "<location unavailable>"),
((None, None, data_pos), "data pos 3"),
((None, line_pos, None), "line 1, column 2"),
((None, line_pos, data_pos), "line 1, column 2 (data pos 3)"),
((path.clone(), None, None), "path '$[1].name'"),
(
(path.clone(), None, data_pos),
"path '$[1].name', data pos 3",
),
(
(path.clone(), line_pos, None),
"path '$[1].name', line 1, column 2",
),
(
(path.clone(), line_pos, data_pos),
"path '$[1].name', line 1, column 2 (data pos 3)",
),
];
for combination in combinations {
let location_data = combination.0;
let reader_location = JsonReaderPosition {
path: location_data.0.clone(),
line_pos: location_data.1,
data_pos: location_data.2,
};
let display_string = reader_location.to_string();
let expected_display_string = combination.1;
assert_eq!(
expected_display_string, display_string,
"expected display string for {location_data:?}: {expected_display_string}"
);
}
}
#[test]
fn unexpected_structure_kind_display() {
assert_eq!(
"TooShortArray(expected_index = 2)",
UnexpectedStructureKind::TooShortArray { expected_index: 2 }.to_string()
);
assert_eq!(
"MissingObjectMember(\"custom-name\")",
UnexpectedStructureKind::MissingObjectMember {
member_name: "custom-name".to_owned()
}
.to_string()
);
assert_eq!(
"FewerElementsThanExpected",
UnexpectedStructureKind::FewerElementsThanExpected.to_string()
);
assert_eq!(
"MoreElementsThanExpected",
UnexpectedStructureKind::MoreElementsThanExpected.to_string()
);
}
}