use alloc::boxed::Box;
use core::num::NonZeroUsize;
#[cfg(doc)]
use crate::RawJson;
use crate::{JsonValueKind, RawJsonValue};
#[derive(Debug)]
pub enum JsonParseError {
UnexpectedEos {
kind: Option<JsonValueKind>,
position: usize,
},
UnexpectedTrailingChar {
kind: JsonValueKind,
position: usize,
},
UnexpectedValueChar {
kind: Option<JsonValueKind>,
position: usize,
},
InvalidValue {
kind: JsonValueKind,
position: usize,
error: Box<dyn Send + Sync + core::error::Error>,
},
}
impl JsonParseError {
pub fn invalid_value<E>(value: RawJsonValue<'_, '_>, error: E) -> JsonParseError
where
E: Into<Box<dyn Send + Sync + core::error::Error>>,
{
JsonParseError::InvalidValue {
kind: value.kind(),
position: value.position(),
error: error.into(),
}
}
pub fn kind(&self) -> Option<JsonValueKind> {
match self {
JsonParseError::UnexpectedEos { kind, .. } => *kind,
JsonParseError::UnexpectedTrailingChar { kind, .. } => Some(*kind),
JsonParseError::UnexpectedValueChar { kind, .. } => *kind,
JsonParseError::InvalidValue { kind, .. } => Some(*kind),
}
}
pub fn position(&self) -> usize {
match self {
JsonParseError::UnexpectedEos { position, .. }
| JsonParseError::UnexpectedTrailingChar { position, .. }
| JsonParseError::UnexpectedValueChar { position, .. }
| JsonParseError::InvalidValue { position, .. } => *position,
}
}
pub fn get_line_and_column_numbers(&self, text: &str) -> Option<(NonZeroUsize, NonZeroUsize)> {
let position = self.position();
if position > text.len() {
return None;
}
if position == text.len() {
let mut line = 0;
let mut column = 0;
for c in text.chars() {
if c == '\n' {
column = 0;
line += 1;
} else {
column += 1;
}
}
let line = NonZeroUsize::MIN.saturating_add(line);
let column = NonZeroUsize::MIN.saturating_add(column);
return Some((line, column));
}
if !text.is_char_boundary(position) {
return None;
}
let mut line = 0;
let mut column = 0;
for (i, c) in text.char_indices() {
if i == position {
let line = NonZeroUsize::MIN.saturating_add(line);
let column = NonZeroUsize::MIN.saturating_add(column);
return Some((line, column));
}
if c == '\n' {
column = 0;
line += 1;
} else {
column += 1;
}
}
None
}
pub fn get_line<'a>(&self, text: &'a str) -> Option<&'a str> {
let position = self.position();
if !text.is_char_boundary(position) {
return None;
}
let start = text[..position].rfind('\n').map(|i| i + 1).unwrap_or(0);
let end = text[position..]
.find('\n')
.map(|i| position + i)
.unwrap_or_else(|| text.len());
Some(&text[start..end])
}
}
impl core::fmt::Display for JsonParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
JsonParseError::UnexpectedEos { kind, position } => {
if let Some(kind) = kind {
write!(
f,
"unexpected EOS while parsing {kind:?} at byte position {position}"
)
} else {
write!(f, "unexpected EOS at byte position {position}")
}
}
JsonParseError::UnexpectedTrailingChar { kind, position } => {
write!(
f,
"unexpected trailing char after parsing {kind:?} at byte position {position}"
)
}
JsonParseError::UnexpectedValueChar { kind, position } => {
if let Some(kind) = kind {
write!(
f,
"unexpected char while parsing {kind:?} at byte position {position}"
)
} else {
write!(f, "unexpected char at byte position {position}")
}
}
JsonParseError::InvalidValue {
kind,
position,
error,
} => {
write!(
f,
"JSON {kind:?} at byte position {position} is invalid: {error}"
)
}
}
}
}
impl core::error::Error for JsonParseError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
if let Self::InvalidValue { error, .. } = self {
Some(&**error)
} else {
None
}
}
}