facet_json/deserialize/
error.rs

1#[cfg(feature = "alloc")]
2use alloc::format;
3#[cfg(feature = "alloc")]
4use alloc::string::String;
5
6#[cfg(feature = "rich-diagnostics")]
7use ariadne::{Color, Config, IndexType, Label, Report, ReportKind, Source};
8#[cfg(feature = "rich-diagnostics")]
9use owo_colors::OwoColorize;
10
11/// A JSON parse error, with context. Never would've guessed huh.
12pub struct JsonParseErrorWithContext<'input> {
13    #[cfg_attr(not(feature = "rich-diagnostics"), allow(dead_code))]
14    input: &'input [u8],
15    pos: usize,
16    kind: JsonErrorKind,
17    path: String,
18}
19
20impl<'input> JsonParseErrorWithContext<'input> {
21    /// Creates a new `JsonParseErrorWithContext`.
22    ///
23    /// # Arguments
24    ///
25    /// * `kind` - The kind of JSON error encountered.
26    /// * `input` - The original input being parsed.
27    /// * `pos` - The position in the input where the error occurred.
28    pub fn new(kind: JsonErrorKind, input: &'input [u8], pos: usize, path: String) -> Self {
29        Self {
30            input,
31            pos,
32            kind,
33            path,
34        }
35    }
36
37    /// Returns a human-readable error message for this JSON error.
38    pub fn message(&self) -> String {
39        match &self.kind {
40            JsonErrorKind::UnexpectedEof(msg) => format!("Unexpected end of file: {}", msg),
41            JsonErrorKind::UnexpectedCharacter(c) => format!("Unexpected character: '{}'", c),
42            JsonErrorKind::NumberOutOfRange(n) => format!("Number out of range: {}", n),
43            JsonErrorKind::StringAsNumber(s) => format!("Expected a string but got number: {}", s),
44            JsonErrorKind::UnknownField(f) => format!("Unknown field: {}", f),
45            JsonErrorKind::InvalidUtf8(e) => format!("Invalid UTF-8 encoding: {}", e),
46        }
47    }
48}
49
50/// An error kind for JSON parsing.
51#[derive(Debug)]
52pub enum JsonErrorKind {
53    /// The input ended unexpectedly while parsing JSON.
54    UnexpectedEof(&'static str),
55    /// An unexpected character was encountered in the input.
56    UnexpectedCharacter(char),
57    /// A number is out of range.
58    NumberOutOfRange(f64),
59    /// An unexpected String was encountered in the input.
60    StringAsNumber(String),
61    /// An unexpected field name was encountered in the input.
62    UnknownField(String),
63    /// A string that could not be built into valid UTF-8 Unicode
64    InvalidUtf8(String),
65}
66
67#[cfg(not(feature = "rich-diagnostics"))]
68impl core::fmt::Display for JsonParseErrorWithContext<'_> {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        write!(
71            f,
72            "{} at byte {} in path {}",
73            self.message(),
74            self.pos,
75            self.path
76        )
77    }
78}
79
80#[cfg(feature = "rich-diagnostics")]
81impl core::fmt::Display for JsonParseErrorWithContext<'_> {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        let Ok(input_str) = core::str::from_utf8(self.input) else {
84            return write!(f, "(JSON input was invalid UTF-8)");
85        };
86
87        let source_id = "json";
88
89        let (span_start, span_end) = match &self.kind {
90            JsonErrorKind::StringAsNumber(s) => (self.pos - s.len(), self.pos),
91            JsonErrorKind::UnknownField(f) => (self.pos - f.len() - 1, self.pos - 1),
92            _ => {
93                let span_end = if self.pos < self.input.len() {
94                    self.pos + 1
95                } else {
96                    self.input.len()
97                };
98                (self.pos, span_end)
99            }
100        };
101
102        let mut report = Report::build(ReportKind::Error, (source_id, span_start..span_end))
103            .with_message(format!("Error at {}", self.path.yellow()))
104            .with_config(Config::new().with_index_type(IndexType::Byte));
105
106        let label = Label::new((source_id, span_start..span_end))
107            .with_message(self.message())
108            .with_color(Color::Red);
109
110        report = report.with_label(label);
111
112        let source = Source::from(input_str);
113
114        let mut writer = Vec::new();
115        let cache = (source_id, &source);
116
117        if report.finish().write(cache, &mut writer).is_err() {
118            return write!(f, "Error formatting with ariadne");
119        }
120
121        if let Ok(output) = String::from_utf8(writer) {
122            write!(f, "{}", output)
123        } else {
124            write!(f, "Error converting ariadne output to string")
125        }
126    }
127}
128
129impl core::fmt::Debug for JsonParseErrorWithContext<'_> {
130    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
131        core::fmt::Display::fmt(self, f)
132    }
133}
134
135impl core::error::Error for JsonParseErrorWithContext<'_> {}