use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::{self, Display};
use facet_reflect::{ReflectError, Span};
use crate::scanner::ScanErrorKind;
#[derive(Debug)]
pub struct JsonError {
pub kind: JsonErrorKind,
pub span: Option<Span>,
pub source_code: Option<String>,
}
impl Display for JsonError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)
}
}
impl std::error::Error for JsonError {}
impl JsonError {
pub const fn new(kind: JsonErrorKind, span: Span) -> Self {
JsonError {
kind,
span: Some(span),
source_code: None,
}
}
pub const fn without_span(kind: JsonErrorKind) -> Self {
JsonError {
kind,
span: None,
source_code: None,
}
}
pub fn with_source(mut self, source: &str) -> Self {
self.source_code = Some(source.to_string());
self
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum JsonErrorKind {
Scan(ScanErrorKind),
ScanWithContext {
error: ScanErrorKind,
expected_type: &'static str,
},
UnexpectedToken {
got: String,
expected: &'static str,
},
UnexpectedEof {
expected: &'static str,
},
TypeMismatch {
expected: &'static str,
got: &'static str,
},
UnknownField {
field: String,
expected: Vec<&'static str>,
suggestion: Option<&'static str>,
},
MissingField {
field: &'static str,
object_start: Option<Span>,
object_end: Option<Span>,
},
InvalidValue {
message: String,
},
Reflect(ReflectError),
NumberOutOfRange {
value: String,
target_type: &'static str,
},
DuplicateKey {
key: String,
},
InvalidUtf8,
Solver(String),
Io(String),
}
impl Display for JsonErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JsonErrorKind::Scan(e) => write!(f, "{e:?}"),
JsonErrorKind::ScanWithContext {
error,
expected_type,
} => {
write!(f, "{error:?} (while parsing {expected_type})")
}
JsonErrorKind::UnexpectedToken { got, expected } => {
write!(f, "unexpected token: got {got}, expected {expected}")
}
JsonErrorKind::UnexpectedEof { expected } => {
write!(f, "unexpected end of input, expected {expected}")
}
JsonErrorKind::TypeMismatch { expected, got } => {
write!(f, "type mismatch: expected {expected}, got {got}")
}
JsonErrorKind::UnknownField {
field,
expected,
suggestion,
} => {
write!(f, "unknown field `{field}`, expected one of: {expected:?}")?;
if let Some(suggested) = suggestion {
write!(f, " (did you mean `{suggested}`?)")?;
}
Ok(())
}
JsonErrorKind::MissingField { field, .. } => {
write!(f, "missing required field `{field}`")
}
JsonErrorKind::InvalidValue { message } => {
write!(f, "invalid value: {message}")
}
JsonErrorKind::Reflect(e) => write!(f, "reflection error: {e}"),
JsonErrorKind::NumberOutOfRange { value, target_type } => {
write!(f, "number `{value}` out of range for {target_type}")
}
JsonErrorKind::DuplicateKey { key } => {
write!(f, "duplicate key `{key}`")
}
JsonErrorKind::InvalidUtf8 => write!(f, "invalid UTF-8 sequence"),
JsonErrorKind::Solver(msg) => write!(f, "solver error: {msg}"),
JsonErrorKind::Io(msg) => write!(f, "I/O error: {msg}"),
}
}
}
impl JsonErrorKind {
pub const fn code(&self) -> &'static str {
match self {
JsonErrorKind::Scan(_) => "json::scan",
JsonErrorKind::ScanWithContext { .. } => "json::scan",
JsonErrorKind::UnexpectedToken { .. } => "json::unexpected_token",
JsonErrorKind::UnexpectedEof { .. } => "json::unexpected_eof",
JsonErrorKind::TypeMismatch { .. } => "json::type_mismatch",
JsonErrorKind::UnknownField { .. } => "json::unknown_field",
JsonErrorKind::MissingField { .. } => "json::missing_field",
JsonErrorKind::InvalidValue { .. } => "json::invalid_value",
JsonErrorKind::Reflect(_) => "json::reflect",
JsonErrorKind::NumberOutOfRange { .. } => "json::number_out_of_range",
JsonErrorKind::DuplicateKey { .. } => "json::duplicate_key",
JsonErrorKind::InvalidUtf8 => "json::invalid_utf8",
JsonErrorKind::Solver(_) => "json::solver",
JsonErrorKind::Io(_) => "json::io",
}
}
pub fn label(&self) -> String {
match self {
JsonErrorKind::Scan(e) => match e {
ScanErrorKind::UnexpectedChar(c) => format!("unexpected '{c}'"),
ScanErrorKind::UnexpectedEof(ctx) => format!("unexpected end of input {ctx}"),
ScanErrorKind::InvalidUtf8 => "invalid UTF-8 here".into(),
},
JsonErrorKind::ScanWithContext {
error,
expected_type,
} => match error {
ScanErrorKind::UnexpectedChar(c) => {
format!("unexpected '{c}', expected {expected_type}")
}
ScanErrorKind::UnexpectedEof(_) => {
format!("unexpected end of input, expected {expected_type}")
}
ScanErrorKind::InvalidUtf8 => "invalid UTF-8 here".into(),
},
JsonErrorKind::UnexpectedToken { got, expected } => {
format!("expected {expected}, got '{got}'")
}
JsonErrorKind::UnexpectedEof { expected } => format!("expected {expected}"),
JsonErrorKind::TypeMismatch { expected, got } => {
format!("expected {expected}, got {got}")
}
JsonErrorKind::UnknownField {
field, suggestion, ..
} => {
if let Some(suggested) = suggestion {
format!("unknown field '{field}' - did you mean '{suggested}'?")
} else {
format!("unknown field '{field}'")
}
}
JsonErrorKind::MissingField { field, .. } => format!("missing field '{field}'"),
JsonErrorKind::InvalidValue { .. } => "invalid value".into(),
JsonErrorKind::Reflect(_) => "reflection error".into(),
JsonErrorKind::NumberOutOfRange { target_type, .. } => {
format!("out of range for {target_type}")
}
JsonErrorKind::DuplicateKey { key } => format!("duplicate key '{key}'"),
JsonErrorKind::InvalidUtf8 => "invalid UTF-8".into(),
JsonErrorKind::Solver(_) => "solver error".into(),
JsonErrorKind::Io(_) => "I/O error".into(),
}
}
}
impl From<ReflectError> for JsonError {
fn from(err: ReflectError) -> Self {
JsonError {
kind: JsonErrorKind::Reflect(err),
span: None,
source_code: None,
}
}
}
#[allow(dead_code)]
pub type Result<T> = core::result::Result<T, JsonError>;