• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 15113146359

19 May 2025 12:36PM UTC coverage: 56.947% (+0.2%) from 56.733%
15113146359

Pull #628

github

web-flow
Merge 37349d055 into 453013232
Pull Request #628: feat(args): convert reflection spans from arg-wise to char-wise

117 of 183 new or added lines in 5 files covered. (63.93%)

3 existing lines in 2 files now uncovered.

9218 of 16187 relevant lines covered (56.95%)

131.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.87
/facet-deserialize/src/error.rs
1
#[cfg(feature = "rich-diagnostics")]
2
use ariadne::{Color, Config, IndexType, Label, Report, ReportKind, Source};
3

4
use alloc::string::String;
5

6
use facet_core::{Shape, Type, UserType};
7
use facet_reflect::{ReflectError, VariantError};
8
use owo_colors::OwoColorize;
9

10
use crate::debug::InputDebug;
11
use crate::{Cooked, Outcome, Span};
12

13
/// A JSON parse error, with context. Never would've guessed huh.
14
#[derive(Debug)]
15
pub struct DeserError<'input, 'shape, C = Cooked> {
16
    /// The input associated with the error.
17
    pub input: alloc::borrow::Cow<'input, [u8]>,
18

19
    /// Where the error occured
20
    pub span: Span<C>,
21

22
    /// The specific error that occurred while parsing the JSON.
23
    pub kind: DeserErrorKind<'shape>,
24

25
    /// The source identifier for error reporting
26
    pub source_id: &'static str,
27
}
28

29
impl<'input, 'shape, C> DeserError<'input, 'shape, C> {
30
    /// Converts the error into an owned error.
NEW
31
    pub fn into_owned(self) -> DeserError<'static, 'shape, C> {
×
32
        DeserError {
×
33
            input: self.input.into_owned().into(),
×
34
            span: self.span,
×
35
            kind: self.kind,
×
36
            source_id: self.source_id,
×
37
        }
×
38
    }
×
39

40
    /// Sets the span of this error
NEW
41
    pub fn with_span<D>(self, span: Span<D>) -> DeserError<'input, 'shape, D> {
×
NEW
42
        DeserError {
×
NEW
43
            input: self.input,
×
NEW
44
            span,
×
NEW
45
            kind: self.kind,
×
NEW
46
            source_id: self.source_id,
×
NEW
47
        }
×
UNCOV
48
    }
×
49
}
50

51
/// An error kind for JSON parsing.
52
#[derive(Debug, PartialEq, Clone)]
53
pub enum DeserErrorKind<'shape> {
54
    /// An unexpected byte was encountered in the input.
55
    UnexpectedByte {
56
        /// The byte that was found.
57
        got: u8,
58
        /// The expected value as a string description.
59
        wanted: &'static str,
60
    },
61

62
    /// An unexpected character was encountered in the input.
63
    UnexpectedChar {
64
        /// The character that was found.
65
        got: char,
66
        /// The expected value as a string description.
67
        wanted: &'static str,
68
    },
69

70
    /// An unexpected outcome was encountered in the input.
71
    UnexpectedOutcome {
72
        /// The outcome that was found.
73
        got: Outcome<'static>,
74
        /// The expected value as a string description.
75
        wanted: &'static str,
76
    },
77

78
    /// The input ended unexpectedly while parsing JSON.
79
    UnexpectedEof {
80
        /// The expected value as a string description.
81
        wanted: &'static str,
82
    },
83

84
    /// Indicates a value was expected to follow an element in the input.
85
    MissingValue {
86
        /// Describes what type of value was expected.
87
        expected: &'static str,
88
        /// The element that requires the missing value.
89
        field: String,
90
    },
91

92
    /// A required struct field was missing at the end of JSON input.
93
    MissingField(&'static str),
94

95
    /// A number is out of range.
96
    NumberOutOfRange(f64),
97

98
    /// An unexpected String was encountered in the input.
99
    StringAsNumber(String),
100

101
    /// An unexpected field name was encountered in the input.
102
    UnknownField {
103
        /// The name of the field that was not recognized
104
        field_name: String,
105

106
        /// The shape definition where the unknown field was encountered
107
        shape: &'shape Shape<'shape>,
108
    },
109

110
    /// A string that could not be built into valid UTF-8 Unicode
111
    InvalidUtf8(String),
112

113
    /// An error occurred while reflecting a type.
114
    ReflectError(ReflectError<'shape>),
115

116
    /// Some feature is not yet implemented (under development).
117
    Unimplemented(&'static str),
118

119
    /// An unsupported type was encountered.
120
    UnsupportedType {
121
        /// The shape we got
122
        got: &'shape Shape<'shape>,
123

124
        /// The shape we wanted
125
        wanted: &'static str,
126
    },
127

128
    /// An enum variant name that doesn't exist in the enum definition.
129
    NoSuchVariant {
130
        /// The name of the variant that was not found
131
        name: String,
132

133
        /// The enum shape definition where the variant was looked up
134
        enum_shape: &'shape Shape<'shape>,
135
    },
136

137
    /// An error occurred when reflecting an enum variant (index) from a user type.
138
    VariantError(VariantError),
139
}
140

141
impl<'input, 'shape, C> DeserError<'input, 'shape, C> {
142
    /// Creates a new deser error, preserving input and location context for accurate reporting.
143
    pub fn new<I>(
52✔
144
        kind: DeserErrorKind<'shape>,
52✔
145
        input: &'input I,
52✔
146
        span: Span<C>,
52✔
147
        source_id: &'static str,
52✔
148
    ) -> Self
52✔
149
    where
52✔
150
        I: ?Sized + 'input + InputDebug,
52✔
151
    {
152
        Self {
52✔
153
            input: input.as_cow(),
52✔
154
            span,
52✔
155
            kind,
52✔
156
            source_id,
52✔
157
        }
52✔
158
    }
52✔
159

160
    /// Constructs a reflection-related deser error, keeping contextual information intact.
161
    pub(crate) fn new_reflect<I>(
22✔
162
        e: ReflectError<'shape>,
22✔
163
        input: &'input I,
22✔
164
        span: Span<C>,
22✔
165
        source_id: &'static str,
22✔
166
    ) -> Self
22✔
167
    where
22✔
168
        I: ?Sized + 'input + InputDebug,
22✔
169
    {
170
        DeserError::new(DeserErrorKind::ReflectError(e), input, span, source_id)
22✔
171
    }
22✔
172

173
    /// Sets the source ID for this error
UNCOV
174
    pub fn with_source_id(mut self, source_id: &'static str) -> Self {
×
175
        self.source_id = source_id;
×
176
        self
×
177
    }
×
178

179
    /// Provides a human-friendly message wrapper to improve error readability.
180
    pub fn message(&self) -> DeserErrorMessage<'_, '_, C> {
40✔
181
        DeserErrorMessage(self)
40✔
182
    }
40✔
183
}
184

185
/// A wrapper type for displaying deser error messages
186
pub struct DeserErrorMessage<'input, 'shape, C = Cooked>(&'input DeserError<'input, 'shape, C>);
187

188
impl core::fmt::Display for DeserErrorMessage<'_, '_> {
189
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40✔
190
        match &self.0.kind {
40✔
191
            DeserErrorKind::UnexpectedByte { got, wanted } => write!(
×
192
                f,
×
193
                "Unexpected byte: got 0x{:02X}, wanted {}",
×
194
                got.red(),
×
195
                wanted.yellow()
×
196
            ),
197
            DeserErrorKind::UnexpectedChar { got, wanted } => write!(
6✔
198
                f,
6✔
199
                "Unexpected character: got '{}', wanted {}",
6✔
200
                got.red(),
6✔
201
                wanted.yellow()
6✔
202
            ),
203
            DeserErrorKind::UnexpectedOutcome { got, wanted } => {
×
204
                write!(f, "Unexpected {}, wanted {}", got.red(), wanted.yellow())
×
205
            }
206
            DeserErrorKind::UnexpectedEof { wanted } => {
×
207
                write!(f, "Unexpected end of file: wanted {}", wanted.red())
×
208
            }
209
            DeserErrorKind::MissingValue { expected, field } => {
5✔
210
                write!(f, "Missing {} for {}", expected.red(), field.yellow())
5✔
211
            }
212
            DeserErrorKind::MissingField(fld) => write!(f, "Missing required field: {}", fld.red()),
×
213
            DeserErrorKind::NumberOutOfRange(n) => {
×
214
                write!(f, "Number out of range: {}", n.red())
×
215
            }
216
            DeserErrorKind::StringAsNumber(s) => {
×
217
                write!(f, "Expected a string but got number: {}", s.red())
×
218
            }
219
            DeserErrorKind::UnknownField { field_name, shape } => {
7✔
220
                write!(
7✔
221
                    f,
7✔
222
                    "Unknown field: {} for shape {}",
7✔
223
                    field_name.red(),
7✔
224
                    shape.yellow()
7✔
225
                )
226
            }
227
            DeserErrorKind::InvalidUtf8(e) => write!(f, "Invalid UTF-8 encoding: {}", e.red()),
×
228
            DeserErrorKind::ReflectError(e) => write!(f, "{e}"),
21✔
229
            DeserErrorKind::Unimplemented(s) => {
×
230
                write!(f, "Feature not yet implemented: {}", s.yellow())
×
231
            }
232
            DeserErrorKind::UnsupportedType { got, wanted } => {
1✔
233
                write!(
1✔
234
                    f,
1✔
235
                    "Unsupported type: got {}, wanted {}",
1✔
236
                    got.red(),
1✔
237
                    wanted.green()
1✔
238
                )
239
            }
240
            DeserErrorKind::NoSuchVariant { name, enum_shape } => {
×
241
                if let Type::User(UserType::Enum(ed)) = enum_shape.ty {
×
242
                    write!(
×
243
                        f,
×
244
                        "Enum variant not found: {} in enum {}. Available variants: [",
×
245
                        name.red(),
×
246
                        enum_shape.yellow()
×
247
                    )?;
×
248

249
                    let mut first = true;
×
250
                    for variant in ed.variants.iter() {
×
251
                        if !first {
×
252
                            write!(f, ", ")?;
×
253
                        }
×
254
                        write!(f, "{}", variant.name.green())?;
×
255
                        first = false;
×
256
                    }
257

258
                    write!(f, "]")?;
×
259
                    Ok(())
×
260
                } else {
261
                    write!(
×
262
                        f,
×
263
                        "Enum variant not found: {} in non-enum type {}",
×
264
                        name.red(),
×
265
                        enum_shape.yellow()
×
266
                    )?;
×
267
                    Ok(())
×
268
                }
269
            }
270
            DeserErrorKind::VariantError(e) => {
×
271
                write!(f, "Variant error: {e}")
×
272
            }
273
        }
274
    }
40✔
275
}
276

277
#[cfg(not(feature = "rich-diagnostics"))]
278
impl core::fmt::Display for DeserError<'_, '_> {
279
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280
        write!(f, "{} at byte {}", self.message(), self.span.start(),)
281
    }
282
}
283

284
#[cfg(feature = "rich-diagnostics")]
285
impl core::fmt::Display for DeserError<'_, '_> {
286
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40✔
287
        let Ok(input_str) = core::str::from_utf8(&self.input[..]) else {
40✔
288
            return write!(f, "(JSON input was invalid UTF-8)");
×
289
        };
290

291
        let source_id = self.source_id;
40✔
292
        let span_start = self.span.start();
40✔
293
        let span_end = self.span.end();
40✔
294

295
        let mut report = Report::build(ReportKind::Error, (source_id, span_start..span_end))
40✔
296
            .with_config(Config::new().with_index_type(IndexType::Byte));
40✔
297

298
        let label = Label::new((source_id, span_start..span_end))
40✔
299
            .with_message(self.message())
40✔
300
            .with_color(Color::Red);
40✔
301

302
        report = report.with_label(label);
40✔
303

304
        let source = Source::from(input_str);
40✔
305

306
        struct FmtWriter<'a, 'b: 'a> {
307
            f: &'a mut core::fmt::Formatter<'b>,
308
            error: Option<core::fmt::Error>,
309
        }
310

311
        impl core::fmt::Write for FmtWriter<'_, '_> {
312
            fn write_str(&mut self, s: &str) -> core::fmt::Result {
9,663✔
313
                if self.error.is_some() {
9,663✔
314
                    // Already failed, do nothing
315
                    return Err(core::fmt::Error);
×
316
                }
9,663✔
317
                if let Err(e) = self.f.write_str(s) {
9,663✔
318
                    self.error = Some(e);
×
319
                    Err(core::fmt::Error)
×
320
                } else {
321
                    Ok(())
9,663✔
322
                }
323
            }
9,663✔
324
        }
325

326
        struct IoWriter<'a, 'b: 'a> {
327
            inner: FmtWriter<'a, 'b>,
328
        }
329

330
        impl std::io::Write for IoWriter<'_, '_> {
331
            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
9,663✔
332
                match core::str::from_utf8(buf) {
9,663✔
333
                    Ok(s) => match core::fmt::Write::write_str(&mut self.inner, s) {
9,663✔
334
                        Ok(()) => Ok(buf.len()),
9,663✔
335
                        Err(_) => Err(std::io::ErrorKind::Other.into()),
×
336
                    },
337
                    Err(_) => Err(std::io::ErrorKind::InvalidData.into()),
×
338
                }
339
            }
9,663✔
340
            fn flush(&mut self) -> std::io::Result<()> {
×
341
                Ok(())
×
342
            }
×
343
        }
344

345
        let cache = (source_id, &source);
40✔
346

347
        let fmt_writer = FmtWriter { f, error: None };
40✔
348
        let mut io_writer = IoWriter { inner: fmt_writer };
40✔
349

350
        if report.finish().write(cache, &mut io_writer).is_err() {
40✔
351
            return write!(f, "Error formatting with ariadne");
×
352
        }
40✔
353

354
        // Check if our adapter ran into a formatting error
355
        if io_writer.inner.error.is_some() {
40✔
356
            return write!(f, "Error writing ariadne output to fmt::Formatter");
×
357
        }
40✔
358

359
        Ok(())
40✔
360
    }
40✔
361
}
362

363
impl core::error::Error for DeserError<'_, '_> {}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc