use core::fmt::{self, Write as _};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum ErrorCode {
UnexpectedEnd,
ExpectedBrace,
ExpectedBracket,
ExpectedQuote,
ExpectedColon,
ExpectedComma,
ExpectedTrue,
ExpectedFalse,
ExpectedNull,
UnexpectedCharacter,
TrailingContent,
ExceededMaxDepth,
DocumentTooLarge,
InvalidHeader,
UnsupportedFeature,
UnsupportedKeyType,
ExpectedObject,
ExpectedArray,
ExpectedString,
ExpectedBool,
ExpectedInteger,
ExpectedBytes,
ElementTypeMismatch,
ExpectedComplex,
ExpectedMatrix,
ExpectedVariant,
NumberOutOfRange,
InvalidNumber,
ExpectedNumber,
InvalidEscape,
InvalidSurrogate,
InvalidUtf8,
ControlCharacterInString,
EscapeInBorrowedString,
ArrayLengthMismatch,
ExpectedSingleChar,
UnknownKey,
UnknownVariant,
MissingKey,
InvalidMatrixShape,
InvalidMatrixLayout,
InvalidPointer,
NoSuchValue,
}
impl ErrorCode {
pub const fn message(self) -> &'static str {
use ErrorCode::*;
match self {
UnexpectedEnd => "unexpected end of input",
ExpectedBrace => "expected '{'",
ExpectedBracket => "expected '['",
ExpectedQuote => "expected '\"'",
ExpectedColon => "expected ':'",
ExpectedComma => "expected ','",
ExpectedTrue => "expected 'true'",
ExpectedFalse => "expected 'false'",
ExpectedNull => "expected 'null'",
UnexpectedCharacter => "unexpected character",
TrailingContent => "trailing content after value",
ExceededMaxDepth => "exceeded maximum nesting depth",
DocumentTooLarge => "value exceeds the streaming size limit",
InvalidHeader => "invalid BEVE header",
UnsupportedFeature => "unsupported BEVE feature",
UnsupportedKeyType => "unsupported object key type",
ExpectedObject => "expected an object",
ExpectedArray => "expected an array",
ExpectedString => "expected a string",
ExpectedBool => "expected a boolean",
ExpectedInteger => "expected an integer",
ExpectedBytes => "expected an array of bytes",
ElementTypeMismatch => "array element type does not match the target",
ExpectedComplex => "expected a complex number",
ExpectedMatrix => "expected a matrix",
ExpectedVariant => "expected an enum variant",
NumberOutOfRange => "number out of range for target type",
InvalidNumber => "invalid number",
ExpectedNumber => "expected a number",
InvalidEscape => "invalid escape sequence",
InvalidSurrogate => "invalid surrogate pair",
InvalidUtf8 => "invalid UTF-8",
ControlCharacterInString => "unescaped control character in string",
EscapeInBorrowedString => "cannot borrow a string containing escapes",
ArrayLengthMismatch => "array length does not match target",
ExpectedSingleChar => "expected a single character",
UnknownKey => "unknown object key",
UnknownVariant => "unknown enum variant",
MissingKey => "missing object key",
InvalidMatrixShape => "matrix extents do not describe its data",
InvalidMatrixLayout => "unknown matrix layout",
InvalidPointer => "invalid JSON Pointer",
NoSuchValue => "no value at that pointer",
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.message())
}
}
impl std::error::Error for ErrorCode {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Error {
pub code: ErrorCode,
pub index: usize,
pub key: Option<&'static str>,
}
impl Error {
#[inline]
pub const fn new(code: ErrorCode, index: usize) -> Self {
Self {
code,
index,
key: None,
}
}
#[inline]
pub const fn with_key(code: ErrorCode, index: usize, key: Option<&'static str>) -> Self {
Self { code, index, key }
}
fn described(&self, f: &mut dyn fmt::Write) -> fmt::Result {
f.write_str(self.code.message())?;
match self.key {
None => Ok(()),
Some(key) => write!(f, " {key:?}"),
}
}
pub fn display_with(&self, input: &str) -> String {
let idx = floor_char_boundary(input, self.index.min(input.len()));
let line_start = input[..idx].rfind('\n').map_or(0, |p| p + 1);
let line_end = input[idx..].find('\n').map_or(input.len(), |p| idx + p);
let line_no = input[..line_start].bytes().filter(|&b| b == b'\n').count() + 1;
let col_no = input[line_start..idx].chars().count() + 1;
let line = &input[line_start..line_end];
const WINDOW: usize = 80;
let rel = idx - line_start;
let (shown, caret_col, elided_left) = if line.len() > WINDOW {
let lo = rel.saturating_sub(WINDOW / 2);
let lo = floor_char_boundary(line, lo);
let hi = ceil_char_boundary(line, (lo + WINDOW).min(line.len()));
(&line[lo..hi], rel - lo, lo > 0)
} else {
(line, rel, false)
};
let caret_col = shown[..caret_col].chars().count();
let mut out = String::new();
let _ = self.described(&mut out);
let _ = writeln!(out, " at line {line_no}, column {col_no}");
if elided_left {
out.push_str("...");
}
out.push_str(shown);
out.push('\n');
if elided_left {
out.push_str(" ");
}
for _ in 0..caret_col {
out.push(' ');
}
out.push('^');
out
}
}
fn floor_char_boundary(s: &str, mut i: usize) -> usize {
while i > 0 && !s.is_char_boundary(i) {
i -= 1;
}
i
}
fn ceil_char_boundary(s: &str, mut i: usize) -> usize {
while i < s.len() && !s.is_char_boundary(i) {
i += 1;
}
i
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.described(f)?;
write!(f, " at byte {}", self.index)
}
}
impl std::error::Error for Error {}
pub type Result<T> = core::result::Result<T, Error>;
pub(crate) type PResult<T> = core::result::Result<T, ErrorCode>;
#[derive(Debug)]
#[non_exhaustive]
pub enum StreamError {
Io(std::io::Error),
Parse(Error),
}
pub type StreamResult<T> = core::result::Result<T, StreamError>;
impl StreamError {
pub fn as_parse(&self) -> Option<&Error> {
match self {
StreamError::Parse(e) => Some(e),
StreamError::Io(_) => None,
}
}
pub fn as_io(&self) -> Option<&std::io::Error> {
match self {
StreamError::Io(e) => Some(e),
StreamError::Parse(_) => None,
}
}
}
impl fmt::Display for StreamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StreamError::Io(e) => write!(f, "i/o error: {e}"),
StreamError::Parse(e) => e.fmt(f),
}
}
}
impl std::error::Error for StreamError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
StreamError::Io(e) => Some(e),
StreamError::Parse(e) => Some(e),
}
}
}
impl From<std::io::Error> for StreamError {
fn from(e: std::io::Error) -> Self {
StreamError::Io(e)
}
}
impl From<Error> for StreamError {
fn from(e: Error) -> Self {
StreamError::Parse(e)
}
}