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