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