1#[cfg(feature = "rich-diagnostics")]
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};
8use facet_reflect::ReflectError;
9use owo_colors::OwoColorize;
10
11use super::{Token, TokenErrorKind, tokenizer::Span};
12use facet_core::Def;
13use facet_core::Shape;
14
15pub struct JsonError<'input> {
17 pub input: alloc::borrow::Cow<'input, [u8]>,
19
20 pub span: Span,
22
23 pub path: String,
25
26 pub kind: JsonErrorKind,
28}
29
30impl<'input> JsonError<'input> {
31 pub fn new(kind: JsonErrorKind, input: &'input [u8], span: Span, path: String) -> Self {
39 Self {
40 input: alloc::borrow::Cow::Borrowed(input),
41 span,
42 kind,
43 path,
44 }
45 }
46
47 pub(crate) fn new_reflect(
48 e: ReflectError,
49 input: &'input [u8],
50 span: Span,
51 path: String,
52 ) -> Self {
53 JsonError::new(JsonErrorKind::ReflectError(e), input, span, path)
54 }
55
56 pub(crate) fn new_syntax(
57 e: TokenErrorKind,
58 input: &'input [u8],
59 span: Span,
60 path: String,
61 ) -> Self {
62 JsonError::new(JsonErrorKind::SyntaxError(e), input, span, path)
63 }
64
65 pub fn message(&self) -> JsonErrorMessage<'_> {
67 JsonErrorMessage(self)
68 }
69}
70
71pub struct JsonErrorMessage<'a>(&'a JsonError<'a>);
73
74impl core::fmt::Display for JsonErrorMessage<'_> {
75 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76 match &self.0.kind {
77 JsonErrorKind::UnexpectedEof(msg) => write!(f, "Unexpected end of file: {}", msg.red()),
78 JsonErrorKind::MissingField(fld) => write!(f, "Missing required field: {}", fld.red()),
79 JsonErrorKind::UnexpectedToken { got, wanted } => {
80 write!(
81 f,
82 "Unexpected token: got {}, wanted {}",
83 got.red(),
84 wanted.green()
85 )
86 }
87 JsonErrorKind::NumberOutOfRange(n) => {
88 write!(f, "Number out of range: {}", n.red())
89 }
90 JsonErrorKind::StringAsNumber(s) => {
91 write!(f, "Expected a string but got number: {}", s.red())
92 }
93 JsonErrorKind::UnknownField { field_name, shape } => {
94 write!(
95 f,
96 "Unknown field: {} for shape {}",
97 field_name.red(),
98 shape.yellow()
99 )
100 }
101 JsonErrorKind::InvalidUtf8(e) => write!(f, "Invalid UTF-8 encoding: {}", e.red()),
102 JsonErrorKind::ReflectError(e) => write!(f, "{e}"),
103 JsonErrorKind::SyntaxError(e) => write!(f, "{e}"),
104 JsonErrorKind::Unimplemented(s) => {
105 write!(f, "Feature not yet implemented: {}", s.yellow())
106 }
107 JsonErrorKind::UnsupportedType { got, wanted } => {
108 write!(
109 f,
110 "Unsupported type: got {}, wanted {}",
111 got.red(),
112 wanted.green()
113 )
114 }
115 JsonErrorKind::NoSuchVariant { name, enum_shape } => match enum_shape.def {
116 Def::Enum(ed) => {
117 write!(
118 f,
119 "Enum variant not found: {} in enum {}. Available variants: [",
120 name.red(),
121 enum_shape.yellow()
122 )?;
123
124 let mut first = true;
125 for variant in ed.variants.iter() {
126 if !first {
127 write!(f, ", ")?;
128 }
129 write!(f, "{}", variant.name.green())?;
130 first = false;
131 }
132
133 write!(f, "]")
134 }
135 _ => {
136 write!(
137 f,
138 "Enum variant not found: {} in enum {}. No variants available (not an enum)",
139 name.red(),
140 enum_shape.yellow()
141 )
142 }
143 },
144 }
145 }
146}
147
148#[derive(Debug, PartialEq, Clone)]
150pub enum JsonErrorKind {
151 UnexpectedEof(&'static str),
153 MissingField(&'static str),
155 UnexpectedToken {
157 got: Token,
159
160 wanted: &'static str,
162 },
163 NumberOutOfRange(f64),
165 StringAsNumber(String),
167 UnknownField {
169 field_name: String,
171 shape: &'static Shape,
173 },
174 InvalidUtf8(String),
176 ReflectError(ReflectError),
178 SyntaxError(TokenErrorKind),
180 Unimplemented(&'static str),
182 UnsupportedType {
184 got: &'static Shape,
186
187 wanted: &'static str,
189 },
190 NoSuchVariant {
192 name: String,
194
195 enum_shape: &'static Shape,
197 },
198}
199
200impl From<ReflectError> for JsonErrorKind {
201 fn from(err: ReflectError) -> Self {
202 JsonErrorKind::ReflectError(err)
203 }
204}
205
206#[cfg(not(feature = "rich-diagnostics"))]
207impl core::fmt::Display for JsonError<'_> {
208 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209 write!(
210 f,
211 "{} at byte {} in path {}",
212 self.message(),
213 self.span.start(),
214 self.path
215 )
216 }
217}
218
219#[cfg(feature = "rich-diagnostics")]
220impl core::fmt::Display for JsonError<'_> {
221 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
222 let Ok(input_str) = core::str::from_utf8(&self.input[..]) else {
223 return write!(f, "(JSON input was invalid UTF-8)");
224 };
225
226 let source_id = "json";
227 let span_start = self.span.start();
228 let span_end = self.span.end();
229
230 let mut report = Report::build(ReportKind::Error, (source_id, span_start..span_end))
231 .with_message(format!("Error at {}", self.path.yellow()))
232 .with_config(Config::new().with_index_type(IndexType::Byte));
233
234 let label = Label::new((source_id, span_start..span_end))
235 .with_message(self.message())
236 .with_color(Color::Red);
237
238 report = report.with_label(label);
239
240 let source = Source::from(input_str);
241
242 let mut writer = Vec::new();
243 let cache = (source_id, &source);
244
245 if report.finish().write(cache, &mut writer).is_err() {
246 return write!(f, "Error formatting with ariadne");
247 }
248
249 if let Ok(output) = String::from_utf8(writer) {
250 write!(f, "{}", output)
251 } else {
252 write!(f, "Error converting ariadne output to string")
253 }
254 }
255}
256
257impl core::fmt::Debug for JsonError<'_> {
258 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
259 core::fmt::Display::fmt(self, f)
260 }
261}
262
263impl core::error::Error for JsonError<'_> {}