#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WriteError {
BufferFull,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParseError {
pub kind: ParseErrorKind,
pub offset: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ParseErrorKind {
UnexpectedToken {
expected: &'static str,
got: &'static str,
},
UnexpectedEof,
InvalidEscape(u8),
StringBufferOverflow,
InvalidUtf8,
UnknownField {
type_name: &'static str,
expected_fields: &'static [&'static str],
},
MissingField { field: &'static str },
KeyHasEscapes,
}
impl ParseError {
pub(crate) fn at(offset: usize, kind: ParseErrorKind) -> Self {
Self { kind, offset }
}
pub fn display_with_source<'a>(&'a self, src: &'a str) -> ParseErrorDisplay<'a> {
ParseErrorDisplay { error: self, src }
}
#[cfg(feature = "std")]
pub fn print(&self, src: &str) {
use std::eprintln;
eprintln!("{}", self.display_with_source(src));
}
}
pub struct ParseErrorDisplay<'a> {
error: &'a ParseError,
src: &'a str,
}
impl core::fmt::Display for ParseErrorDisplay<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fn visual_width(s: &str) -> usize {
s.chars().fold(0, |acc, c| if c == '\t' { (acc / 4 + 1) * 4 } else { acc + 1 })
}
fn count_digits(mut n: usize) -> usize {
if n == 0 { return 1; }
let mut d = 0;
while n > 0 { d += 1; n /= 10; }
d
}
let src = self.src;
let offset = self.error.offset.min(src.len());
let bytes = src.as_bytes();
let line_start = bytes[..offset]
.iter()
.rposition(|&b| b == b'\n')
.map_or(0, |p| p + 1);
let line_end = bytes[offset..]
.iter()
.position(|&b| b == b'\n')
.map_or(src.len(), |p| offset + p);
let raw_line = &src[line_start..line_end];
let line = raw_line.strip_suffix('\r').unwrap_or(raw_line);
let line_number = bytes[..line_start].iter().filter(|&&b| b == b'\n').count() + 1;
let visual_col = visual_width(&src[line_start..offset]);
let total_visual = visual_width(line);
const MAX_WIDTH: usize = 80;
let (win_start, win_end) = if total_visual <= MAX_WIDTH {
(0, total_visual)
} else {
let s = visual_col.saturating_sub(MAX_WIDTH / 2);
let e = (s + MAX_WIDTH).min(total_visual);
(s, e)
};
let left_dots = win_start > 0;
let right_dots = win_end < total_visual;
let digits = count_digits(line_number);
write!(f, "{} | ", line_number)?;
if left_dots { f.write_str("...")?; }
let mut vis = 0usize;
for c in line.chars() {
if vis >= win_end { break; }
let w = if c == '\t' { (vis / 4 + 1) * 4 - vis } else { 1 };
if vis >= win_start {
if c == '\t' {
for _ in 0..w { f.write_str(" ")?; }
} else {
write!(f, "{c}")?;
}
}
vis += w;
}
if right_dots { f.write_str("...")?; }
writeln!(f)?;
let col_in_window = (visual_col - win_start) + if left_dots { 3 } else { 0 };
for _ in 0..digits { f.write_str(" ")?; }
f.write_str(" | ")?;
for _ in 0..col_in_window { f.write_str(" ")?; }
writeln!(f, "^")?;
for _ in 0..digits { f.write_str(" ")?; }
f.write_str(" | ")?;
for _ in 0..col_in_window { f.write_str(" ")?; }
write!(f, "{}", self.error.kind)
}
}
impl core::fmt::Display for WriteError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
WriteError::BufferFull => f.write_str("output buffer is full"),
}
}
}
impl core::fmt::Display for ParseErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ParseErrorKind::UnexpectedToken { expected, got } =>
write!(f, "expected {expected}, got {got}"),
ParseErrorKind::UnexpectedEof =>
f.write_str("unexpected end of input"),
ParseErrorKind::InvalidEscape(b) =>
write!(f, "invalid escape: \\{}", *b as char),
ParseErrorKind::StringBufferOverflow =>
f.write_str("string exceeds scratch buffer"),
ParseErrorKind::InvalidUtf8 =>
f.write_str("invalid UTF-8 in string"),
ParseErrorKind::UnknownField { type_name, expected_fields } => {
if type_name.is_empty() {
f.write_str("unknown field")?;
} else {
write!(f, "unknown field in `{type_name}`")?;
}
match expected_fields {
[] => Ok(()),
[only] => write!(f, ", expected `{only}`"),
fields => {
f.write_str(", expected one of: ")?;
for (i, name) in fields.iter().enumerate() {
if i > 0 { f.write_str(", ")?; }
write!(f, "`{name}`")?;
}
Ok(())
}
}
}
ParseErrorKind::MissingField { field } =>
write!(f, "missing required field `{field}`"),
ParseErrorKind::KeyHasEscapes =>
f.write_str("object key contains escape sequences"),
}
}
}
impl core::fmt::Display for ParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "parse error at offset {}: {}", self.offset, self.kind)
}
}
impl core::error::Error for WriteError {}
impl core::error::Error for ParseError {}