1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
#[cfg(feature = "rich-diagnostics")]
use ariadne::{Color, Config, IndexType, Label, Report, ReportKind, Source};
use alloc::{borrow::Cow, string::String};
use facet_core::{Shape, Type, UserType};
use facet_reflect::{ReflectError, VariantError};
use owo_colors::OwoColorize;
use crate::{Outcome, Span};
/// A deserialization error, associated with a specific input and location.
pub struct DeserError<'input> {
/// The input associated with the error.
pub input: Cow<'input, [u8]>,
/// Where the error occured
pub span: Span,
/// The specific error that occurred
pub kind: DeserErrorKind,
}
impl<'input> DeserError<'input> {
/// Converts the error into an owned error.
pub fn into_owned(self) -> DeserError<'static> {
DeserError {
input: self.input.into_owned().into(),
span: self.span,
kind: self.kind,
}
}
/// Sets the span of this error
pub fn with_span(self, span: Span) -> DeserError<'input> {
DeserError {
input: self.input,
span,
kind: self.kind,
}
}
}
/// An error kind for JSON parsing.
#[derive(Debug, Clone)]
pub enum DeserErrorKind {
/// An unexpected byte was encountered in the input.
UnexpectedByte {
/// The byte that was found.
got: u8,
/// The expected value as a string description.
wanted: &'static str,
},
/// An unexpected character was encountered in the input.
UnexpectedChar {
/// The character that was found.
got: char,
/// The expected value as a string description.
wanted: &'static str,
},
/// An unexpected outcome was encountered in the input.
UnexpectedOutcome {
/// The outcome that was found.
got: Outcome<'static>,
/// The expected value as a string description.
wanted: &'static str,
},
/// The input ended unexpectedly while parsing JSON.
UnexpectedEof {
/// The expected value as a string description.
wanted: &'static str,
},
/// Indicates a value was expected to follow an element in the input.
MissingValue {
/// Describes what type of value was expected.
expected: &'static str,
/// The element that requires the missing value.
field: String,
},
/// A required struct field was missing at the end of JSON input.
MissingField(&'static str),
/// A number is out of range.
NumberOutOfRange(f64),
/// An unexpected String was encountered in the input.
StringAsNumber(String),
/// An unexpected field name was encountered in the input.
UnknownField {
/// The name of the field that was not recognized
field_name: String,
/// The shape definition where the unknown field was encountered
shape: &'static Shape,
},
/// A string that could not be built into valid UTF-8 Unicode
InvalidUtf8(String),
/// An error occurred while reflecting a type.
ReflectError(ReflectError),
/// Some feature is not yet implemented (under development).
Unimplemented(&'static str),
/// An unsupported type was encountered.
UnsupportedType {
/// The shape we got
got: &'static Shape,
/// The shape we wanted
wanted: &'static str,
},
/// An enum variant name that doesn't exist in the enum definition.
NoSuchVariant {
/// The name of the variant that was not found
name: String,
/// The enum shape definition where the variant was looked up
enum_shape: &'static Shape,
},
/// An error occurred when reflecting an enum variant (index) from a user type.
VariantError(VariantError),
/// Too many elements for an array.
ArrayOverflow {
/// The array shape
shape: &'static Shape,
/// Maximum allowed length
max_len: usize,
},
/// Failed to convert numeric type.
NumericConversion {
/// Source type name
from: &'static str,
/// Target type name
to: &'static str,
},
}
impl<'input> DeserError<'input> {
/// Creates a new deser error, preserving input and location context for accurate reporting.
pub fn new(kind: DeserErrorKind, input: &'input [u8], span: Span) -> Self {
Self {
input: input.into(),
span,
kind,
}
}
/// Constructs a reflection-related deser error, keeping contextual information intact.
pub(crate) fn new_reflect(e: ReflectError, input: &'input [u8], span: Span) -> Self {
DeserError::new(DeserErrorKind::ReflectError(e), input, span)
}
/// Provides a human-friendly message wrapper to improve error readability.
pub fn message(&self) -> DeserErrorMessage<'_> {
DeserErrorMessage(self)
}
}
/// A wrapper type for displaying deser error messages
pub struct DeserErrorMessage<'input>(&'input DeserError<'input>);
impl core::fmt::Display for DeserErrorMessage<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match &self.0.kind {
DeserErrorKind::UnexpectedByte { got, wanted } => write!(
f,
"Unexpected byte: got 0x{:02X}, wanted {}",
got.red(),
wanted.yellow()
),
DeserErrorKind::UnexpectedChar { got, wanted } => write!(
f,
"Unexpected character: got '{}', wanted {}",
got.red(),
wanted.yellow()
),
DeserErrorKind::UnexpectedOutcome { got, wanted } => {
write!(f, "Unexpected {}, wanted {}", got.red(), wanted.yellow())
}
DeserErrorKind::UnexpectedEof { wanted } => {
write!(f, "Unexpected end of file: wanted {}", wanted.red())
}
DeserErrorKind::MissingValue { expected, field } => {
write!(f, "Missing {} for {}", expected.red(), field.yellow())
}
DeserErrorKind::MissingField(fld) => write!(f, "Missing required field: {}", fld.red()),
DeserErrorKind::NumberOutOfRange(n) => {
write!(f, "Number out of range: {}", n.red())
}
DeserErrorKind::StringAsNumber(s) => {
write!(f, "Expected a string but got number: {}", s.red())
}
DeserErrorKind::UnknownField { field_name, shape } => {
write!(
f,
"Unknown field: {} for shape {}",
field_name.red(),
shape.yellow()
)
}
DeserErrorKind::InvalidUtf8(e) => write!(f, "Invalid UTF-8 encoding: {}", e.red()),
DeserErrorKind::ReflectError(e) => write!(f, "{e}"),
DeserErrorKind::Unimplemented(s) => {
write!(f, "Feature not yet implemented: {}", s.yellow())
}
DeserErrorKind::UnsupportedType { got, wanted } => {
write!(
f,
"Unsupported type: got {}, wanted {}",
got.red(),
wanted.green()
)
}
DeserErrorKind::NoSuchVariant { name, enum_shape } => {
if let Type::User(UserType::Enum(ed)) = enum_shape.ty {
write!(
f,
"Enum variant not found: {} in enum {}. Available variants: [",
name.red(),
enum_shape.yellow()
)?;
let mut first = true;
for variant in ed.variants.iter() {
if !first {
write!(f, ", ")?;
}
write!(f, "{}", variant.name.green())?;
first = false;
}
write!(f, "]")?;
Ok(())
} else {
write!(
f,
"Enum variant not found: {} in non-enum type {}",
name.red(),
enum_shape.yellow()
)?;
Ok(())
}
}
DeserErrorKind::VariantError(e) => {
write!(f, "Variant error: {e}")
}
DeserErrorKind::ArrayOverflow { shape, max_len } => {
write!(
f,
"Too many elements for array {}: maximum {} elements allowed",
shape.blue(),
max_len.yellow()
)
}
DeserErrorKind::NumericConversion { from, to } => {
write!(
f,
"Cannot convert {} to {}: value out of range or precision loss",
from.red(),
to.green()
)
}
}
}
}
#[cfg(not(feature = "rich-diagnostics"))]
impl core::fmt::Display for DeserError<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} at byte {}", self.message(), self.span.start(),)
}
}
#[cfg(feature = "rich-diagnostics")]
impl core::fmt::Display for DeserError<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Try to convert input to utf8 for source display, otherwise fallback to error
let Ok(orig_input_str) = core::str::from_utf8(&self.input[..]) else {
return write!(f, "(JSON input was invalid UTF-8)");
};
let mut span_start = self.span.start();
let mut span_end = self.span.end();
use alloc::borrow::Cow;
let mut input_str: Cow<'_, str> = Cow::Borrowed(orig_input_str);
// --- Context-sensitive truncation logic ---
// When the error occurs very far into a huge (often one-line) input,
// such as minified JSON, it's annoying to display hundreds or thousands of
// preceding and trailing characters. Instead, we seek to trim the displayed
// "source" to just enough around the offending line/location, but only if
// we can do this cleanly.
//
// Our approach:
// - Find the full line that `span_start` is within, using memchr for newlines before and after.
// - Only proceed if both `span_start` and `span_end` are within this line (i.e., error doesn't span lines).
// - If there are more than 180 characters before/after the span on this line, truncate to show
// "...<80 chars>SPANTEXT<80 chars>..." and adjust the display offsets to ensure ariadne points
// to the correct span inside the trimmed display.
//
// Rationale: this avoids a sea of whitespace for extremely long lines (common in compact JSON).
let mut did_truncate = false;
{
// Find the line bounds containing span_start
let bytes = self.input.as_ref();
let line_start = bytes[..span_start]
.iter()
.rposition(|&b| b == b'\n')
.map(|pos| pos + 1)
.unwrap_or(0);
let line_end = bytes[span_start..]
.iter()
.position(|&b| b == b'\n')
.map(|pos| span_start + pos)
.unwrap_or(bytes.len());
// Check if span fits within one line
if span_end <= line_end {
// How much context do we have before and after the span in this line?
let before_chars = span_start - line_start;
let after_chars = line_end.saturating_sub(span_end);
// Only trim if context is long enough
if before_chars > 180 || after_chars > 180 {
let trim_left = if before_chars > 180 {
before_chars - 80
} else {
0
};
let trim_right = if after_chars > 180 {
after_chars - 80
} else {
0
};
let new_start = line_start + trim_left;
let new_end = line_end - trim_right;
let truncated = &orig_input_str[new_start..new_end];
let left_ellipsis = if trim_left > 0 { "…" } else { "" };
let right_ellipsis = if trim_right > 0 { "…" } else { "" };
let mut buf = String::with_capacity(
left_ellipsis.len() + truncated.len() + right_ellipsis.len(),
);
buf.push_str(left_ellipsis);
buf.push_str(truncated);
buf.push_str(right_ellipsis);
// Adjust span offsets to align with the trimmed string
span_start = span_start - new_start + left_ellipsis.len();
span_end = span_end - new_start + left_ellipsis.len();
input_str = Cow::Owned(buf);
did_truncate = true; // mark that truncation occurred
// Done!
}
}
// If the span goes across lines or we cannot cleanly trim, display the full input as fallback
}
if did_truncate {
writeln!(
f,
"{}",
"WARNING: Input was truncated for display. Byte indexes in the error below do not match original input.".yellow().bold()
)?;
}
let mut report = Report::build(ReportKind::Error, span_start..span_end)
.with_config(Config::new().with_index_type(IndexType::Byte));
let label = Label::new(span_start..span_end)
.with_message(self.message())
.with_color(Color::Red);
report = report.with_label(label);
let source = Source::from(input_str);
struct FmtWriter<'a, 'b: 'a> {
f: &'a mut core::fmt::Formatter<'b>,
error: Option<core::fmt::Error>,
}
impl core::fmt::Write for FmtWriter<'_, '_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.error.is_some() {
// Already failed, do nothing
return Err(core::fmt::Error);
}
if let Err(e) = self.f.write_str(s) {
self.error = Some(e);
Err(core::fmt::Error)
} else {
Ok(())
}
}
}
struct IoWriter<'a, 'b: 'a> {
inner: FmtWriter<'a, 'b>,
}
impl std::io::Write for IoWriter<'_, '_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match core::str::from_utf8(buf) {
Ok(s) => match core::fmt::Write::write_str(&mut self.inner, s) {
Ok(()) => Ok(buf.len()),
Err(_) => Err(std::io::ErrorKind::Other.into()),
},
Err(_) => Err(std::io::ErrorKind::InvalidData.into()),
}
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let cache = &source;
let fmt_writer = FmtWriter { f, error: None };
let mut io_writer = IoWriter { inner: fmt_writer };
if report.finish().write(cache, &mut io_writer).is_err() {
return write!(f, "Error formatting with ariadne");
}
// Check if our adapter ran into a formatting error
if io_writer.inner.error.is_some() {
return write!(f, "Error writing ariadne output to fmt::Formatter");
}
Ok(())
}
}
impl core::fmt::Debug for DeserError<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::error::Error for DeserError<'_> {}