use facet_core::Shape;
use facet_path::Path;
use facet_reflect::{AllocError, ReflectError, ReflectErrorKind, ShapeMismatchError, Span};
use std::borrow::Cow;
use std::cell::Cell;
use std::fmt;
thread_local! {
static CURRENT_SPAN: Cell<Option<Span>> = const { Cell::new(None) };
}
pub struct SpanGuard {
prev: Option<Span>,
}
impl SpanGuard {
#[inline]
pub fn new(span: Span) -> Self {
let prev = CURRENT_SPAN.with(|cell| cell.replace(Some(span)));
Self { prev }
}
}
impl Drop for SpanGuard {
fn drop(&mut self) {
CURRENT_SPAN.with(|cell| cell.set(self.prev));
}
}
#[inline]
fn current_span() -> Span {
CURRENT_SPAN.with(|cell| {
cell.get().expect(
"current_span called without an active SpanGuard - this is a bug in the deserializer",
)
})
}
#[derive(Debug)]
pub struct ParseError {
pub span: Span,
pub kind: DeserializeErrorKind,
}
impl ParseError {
#[inline]
pub const fn new(span: Span, kind: DeserializeErrorKind) -> Self {
Self { span, kind }
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at {:?}", self.kind, self.span)
}
}
impl std::error::Error for ParseError {}
impl From<ParseError> for DeserializeError {
fn from(e: ParseError) -> Self {
DeserializeError {
span: Some(e.span),
path: None,
kind: e.kind,
}
}
}
pub struct DeserializeError {
pub span: Option<Span>,
pub path: Option<Path>,
pub kind: DeserializeErrorKind,
}
impl fmt::Debug for DeserializeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let span_str = match self.span {
Some(span) => format!("[{}..{})", span.offset, span.offset + span.len),
None => "none".to_string(),
};
let path_str = match &self.path {
Some(path) => format!("{path}"),
None => "none".to_string(),
};
write!(
f,
"DeserializeError {{ span: {}, path: {}, kind: {} }}",
span_str, path_str, self.kind
)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum DeserializeErrorKind {
UnexpectedChar {
ch: char,
expected: &'static str,
},
UnexpectedEof {
expected: &'static str,
},
InvalidUtf8 {
context: [u8; 16],
context_len: u8,
},
UnexpectedToken {
got: Cow<'static, str>,
expected: &'static str,
},
TypeMismatch {
expected: &'static Shape,
got: Cow<'static, str>,
},
ShapeMismatch {
expected: &'static Shape,
got: &'static Shape,
},
UnknownField {
field: Cow<'static, str>,
suggestion: Option<&'static str>,
},
UnknownVariant {
variant: Cow<'static, str>,
enum_shape: &'static Shape,
},
NoMatchingVariant {
enum_shape: &'static Shape,
input_kind: &'static str,
},
MissingField {
field: &'static str,
container_shape: &'static Shape,
},
DuplicateField {
field: Cow<'static, str>,
first_span: Option<Span>,
},
NumberOutOfRange {
value: Cow<'static, str>,
target_type: &'static str,
},
InvalidValue {
message: Cow<'static, str>,
},
CannotBorrow {
reason: Cow<'static, str>,
},
Reflect {
kind: ReflectErrorKind,
context: &'static str,
},
Unsupported {
message: Cow<'static, str>,
},
Io {
message: Cow<'static, str>,
},
Solver {
message: Cow<'static, str>,
},
Validation {
field: &'static str,
message: Cow<'static, str>,
},
Bug {
error: Cow<'static, str>,
context: &'static str,
},
Alloc {
shape: &'static Shape,
operation: &'static str,
},
Materialize {
expected: &'static Shape,
actual: &'static Shape,
},
RawCaptureNotSupported {
shape: &'static Shape,
},
}
impl fmt::Display for DeserializeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(ref path) = self.path {
write!(f, " at {path:?}")?;
}
Ok(())
}
}
impl fmt::Display for DeserializeErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DeserializeErrorKind::UnexpectedChar { ch, expected } => {
write!(f, "unexpected character {ch:?}, expected {expected}")
}
DeserializeErrorKind::UnexpectedEof { expected } => {
write!(f, "unexpected end of input, expected {expected}")
}
DeserializeErrorKind::UnexpectedToken { got, expected } => {
write!(f, "unexpected token: got {got}, expected {expected}")
}
DeserializeErrorKind::InvalidUtf8 {
context,
context_len,
} => {
let len = (*context_len as usize).min(16);
if len > 0 {
write!(f, "invalid UTF-8 near: {:?}", &context[..len])
} else {
write!(f, "invalid UTF-8")
}
}
DeserializeErrorKind::TypeMismatch { expected, got } => {
write!(f, "type mismatch: expected {expected}, got {got}")
}
DeserializeErrorKind::ShapeMismatch { expected, got } => {
write!(f, "shape mismatch: expected {expected}, got {got}")
}
DeserializeErrorKind::UnknownField { field, suggestion } => {
write!(f, "unknown field `{field}`")?;
if let Some(s) = suggestion {
write!(f, " (did you mean `{s}`?)")?;
}
Ok(())
}
DeserializeErrorKind::UnknownVariant {
variant,
enum_shape,
} => {
write!(f, "unknown variant `{variant}` for enum `{enum_shape}`")
}
DeserializeErrorKind::NoMatchingVariant {
enum_shape,
input_kind,
} => {
write!(
f,
"no matching variant found for enum `{enum_shape}` with {input_kind} input"
)
}
DeserializeErrorKind::MissingField {
field,
container_shape,
} => {
write!(f, "missing field `{field}` in type `{container_shape}`")
}
DeserializeErrorKind::DuplicateField { field, .. } => {
write!(f, "duplicate field `{field}`")
}
DeserializeErrorKind::NumberOutOfRange { value, target_type } => {
write!(f, "number `{value}` out of range for {target_type}")
}
DeserializeErrorKind::InvalidValue { message } => {
write!(f, "invalid value: {message}")
}
DeserializeErrorKind::CannotBorrow { reason } => write!(f, "{reason}"),
DeserializeErrorKind::Reflect { kind, context } => {
if context.is_empty() {
write!(f, "{kind}")
} else {
write!(f, "{kind} (while {context})")
}
}
DeserializeErrorKind::Unsupported { message } => write!(f, "unsupported: {message}"),
DeserializeErrorKind::Io { message } => write!(f, "I/O error: {message}"),
DeserializeErrorKind::Solver { message } => write!(f, "solver error: {message}"),
DeserializeErrorKind::Validation { field, message } => {
write!(f, "validation failed for field `{field}`: {message}")
}
DeserializeErrorKind::Bug { error, context } => {
write!(f, "internal error: {error} while {context}")
}
DeserializeErrorKind::Alloc { shape, operation } => {
write!(f, "allocation failed for {shape}: {operation}")
}
DeserializeErrorKind::Materialize { expected, actual } => {
write!(
f,
"shape mismatch when materializing: expected {expected}, got {actual}"
)
}
DeserializeErrorKind::RawCaptureNotSupported { shape: type_name } => {
write!(
f,
"raw capture not supported: type `{type_name}` requires raw capture, \
but the parser does not support it (e.g., streaming mode without buffering)"
)
}
}
}
}
impl std::error::Error for DeserializeError {}
impl From<ReflectError> for DeserializeError {
fn from(e: ReflectError) -> Self {
let kind = match e.kind {
ReflectErrorKind::UninitializedField { shape, field_name } => {
DeserializeErrorKind::MissingField {
field: field_name,
container_shape: shape,
}
}
other => DeserializeErrorKind::Reflect {
kind: other,
context: "",
},
};
DeserializeError {
span: Some(current_span()),
path: Some(e.path),
kind,
}
}
}
impl From<AllocError> for DeserializeError {
fn from(e: AllocError) -> Self {
DeserializeError {
span: None,
path: None,
kind: DeserializeErrorKind::Alloc {
shape: e.shape,
operation: e.operation,
},
}
}
}
impl From<ShapeMismatchError> for DeserializeError {
fn from(e: ShapeMismatchError) -> Self {
DeserializeError {
span: None,
path: None,
kind: DeserializeErrorKind::Materialize {
expected: e.expected,
actual: e.actual,
},
}
}
}
impl DeserializeErrorKind {
#[inline]
pub const fn with_span(self, span: Span) -> DeserializeError {
DeserializeError {
span: Some(span),
path: None,
kind: self,
}
}
}
impl DeserializeError {
#[inline]
pub fn set_span(mut self, span: Span) -> Self {
self.span = Some(span);
self
}
#[inline]
pub fn set_path(mut self, path: Path) -> Self {
self.path = Some(path);
self
}
#[inline]
pub const fn path(&self) -> Option<&Path> {
self.path.as_ref()
}
#[inline]
pub const fn span(&self) -> Option<&Span> {
self.span.as_ref()
}
#[inline]
pub fn with_path(mut self, new_path: Path) -> Self {
self.path = Some(new_path);
self
}
}
#[cfg(feature = "ariadne")]
mod ariadne_impl {
use super::*;
use ariadne::{Color, Label, Report, ReportKind, Source};
use std::io::Write;
impl DeserializeError {
pub fn to_pretty(&self, filename: &str, source: &str) -> String {
let mut buf = Vec::new();
self.write_pretty(&mut buf, filename, source)
.expect("writing to Vec<u8> should never fail");
String::from_utf8(buf).expect("ariadne output should be valid UTF-8")
}
pub fn write_pretty<W: Write>(
&self,
writer: &mut W,
filename: &str,
source: &str,
) -> std::io::Result<()> {
let (offset, len) = match self.span {
Some(span) => (span.offset as usize, span.len as usize),
None => (0, 0),
};
let offset = offset.min(source.len());
let end = (offset + len).min(source.len());
let range = offset..end.max(offset + 1).min(source.len());
let message = self.kind.to_string();
let mut report =
Report::build(ReportKind::Error, (filename, range.clone())).with_message(&message);
let label = Label::new((filename, range))
.with_message(&message)
.with_color(Color::Red);
report = report.with_label(label);
if let Some(ref path) = self.path {
report = report.with_note(format!("at path: {path}"));
}
report
.finish()
.write((filename, Source::from(source)), writer)
}
pub fn eprint(&self, filename: &str, source: &str) {
let _ = self.write_pretty(&mut std::io::stderr(), filename, source);
}
}
}