Skip to main content

structio/
error.rs

1//! Error types.
2//!
3//! Hot paths propagate a bare [`ErrorCode`], which is a single byte, so
4//! `Result<(), ErrorCode>` is register sized and `?` costs a test-and-branch.
5//! The byte offset is attached once, at the public entry point, from the
6//! cursor position at the moment the parse stopped.
7
8use core::fmt::{self, Write as _};
9
10/// What went wrong. One byte, so error propagation stays cheap.
11///
12/// One set covers both formats, and a code does not say which one produced it.
13/// A few belong to one format by construction, [`ExpectedBrace`](Self::ExpectedBrace)
14/// to JSON and [`InvalidHeader`](Self::InvalidHeader) to BEVE, but most of the
15/// set is shared and which ones are is not a promise. The entry point is what
16/// names the format, so code that needs to report which codec failed should
17/// record it where it chose the codec.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19#[repr(u8)]
20#[non_exhaustive]
21pub enum ErrorCode {
22    // Structural
23    UnexpectedEnd,
24    ExpectedBrace,
25    ExpectedBracket,
26    ExpectedQuote,
27    ExpectedColon,
28    ExpectedComma,
29    ExpectedTrue,
30    ExpectedFalse,
31    ExpectedNull,
32    UnexpectedCharacter,
33    TrailingContent,
34    ExceededMaxDepth,
35    /// A streaming reader would have had to buffer more than its configured
36    /// limit to hold one value. See [`Documents::max_value`].
37    ///
38    /// [`Documents::max_value`]: crate::Documents::max_value
39    DocumentTooLarge,
40
41    // BEVE structure
42    /// A header byte named a type, width, or extension this crate cannot read,
43    /// or was the [delimiter](crate::beve::header::DELIMITER) where a value
44    /// belongs. Also an aligned complex array whose inner array is not the
45    /// one its class allows: not aligned, of another element type, or holding
46    /// an odd number of components.
47    InvalidHeader,
48    /// Padding broke the specification's rule for it. A packed-boolean array
49    /// set a bit past its last element, where the unused high bits of the
50    /// final byte must be zero, so each set one would be another encoding of
51    /// the same array. Or an aligned typed array stated a padding length of
52    /// its element's width or more, where the specification allows only
53    /// `0..width`; in an aligned complex array the element is one component.
54    /// The header is well formed; what follows it is not.
55    InvalidPadding,
56    /// A well-formed BEVE construct with nowhere to go: a 128-bit float, an
57    /// extension beyond the four the specification defines, or, when
58    /// [transcoding](crate::transcode), the deprecated type tag.
59    UnsupportedFeature,
60    /// An object's keys were of a kind the destination cannot take, such as
61    /// integer keys for a struct.
62    UnsupportedKeyType,
63
64    // Type mismatches
65    ExpectedObject,
66    ExpectedArray,
67    ExpectedString,
68    ExpectedBool,
69    ExpectedInteger,
70    /// An array of one-byte elements was expected, for a borrowed `&[u8]`.
71    ExpectedBytes,
72    /// A typed array's stored element type was not the one a bulk read needs.
73    ///
74    /// Reading is otherwise lenient about width: an `i64` field takes a stored
75    /// `i16` and a `f64` takes a stored `f32`, because the value fits. That
76    /// leniency is a conversion done one element at a time, so the paths that
77    /// exist to move a whole block at once cannot offer it, and say this
78    /// instead of quietly becoming the slow path. See
79    /// [`read_beve_array_into`](crate::read_beve_array_into).
80    ElementTypeMismatch,
81    /// A [`Complex`](crate::Complex) was expected but the value was neither a
82    /// complex extension nor a two-element array.
83    ExpectedComplex,
84    /// A BEVE value was read as a [`Matrix`](crate::Matrix) but was neither a
85    /// matrix extension nor an object. BEVE only: the JSON reader wants an
86    /// object like any other and says [`ExpectedBrace`](Self::ExpectedBrace).
87    ExpectedMatrix,
88    /// An enum was neither a variant name nor an object holding exactly one
89    /// member that names one.
90    ///
91    /// The two forms are the whole encoding: a variant carrying nothing is its
92    /// name, and a variant carrying a value is that name used as the single
93    /// key of an object. Anything else, an object with no members or with two,
94    /// a number, an array, is not a variant at all.
95    ExpectedVariant,
96    /// An internally tagged enum's object did not begin with its tag.
97    ///
98    /// An internally tagged enum, declared
99    /// [`tagged_enum!`](crate::tagged_enum)`(.. as tag "..")`, reads in one
100    /// pass, so the member deciding which variant this is has to arrive before
101    /// the members whose meaning it decides. An object whose first member is
102    /// some other key is refused here rather than searched: finding a later tag
103    /// would mean holding the object somewhere or walking it twice, and this
104    /// crate does neither.
105    ///
106    /// The same code covers an object with no members at all, and a tag whose
107    /// value is not a string. Which of the three it was is not distinguished,
108    /// because distinguishing them is the search being refused. The reported
109    /// position is the object's first member, or the object itself when it has
110    /// none.
111    ///
112    /// A tag that *is* first and names nothing is
113    /// [`UnknownVariant`](Self::UnknownVariant): the tag was found, and its
114    /// value is not a variant.
115    ExpectedTag,
116
117    // Values
118    NumberOutOfRange,
119    InvalidNumber,
120    ExpectedNumber,
121    InvalidEscape,
122    InvalidSurrogate,
123    InvalidUtf8,
124    ControlCharacterInString,
125    /// A borrowed `&str` was requested but the JSON string contained escapes,
126    /// so no subslice of the input can represent it.
127    EscapeInBorrowedString,
128    /// A fixed-size target (`[T; N]`, a tuple) did not match the JSON length.
129    ArrayLengthMismatch,
130    /// A `char` was requested but the string was not exactly one scalar value.
131    ExpectedSingleChar,
132    /// An object held a key that no field of the destination claims, under the
133    /// default [`Options::ERROR_ON_UNKNOWN_KEYS`](crate::Options::ERROR_ON_UNKNOWN_KEYS).
134    ///
135    /// Read with [`SkipUnknown`](crate::SkipUnknown) to step over it instead.
136    ///
137    /// The reported position is the key itself, so the message names what was
138    /// not recognized. That holds for a struct declared with
139    /// [`object!`](crate::object) and for the readers written by hand, which
140    /// wind back to the key before refusing: a map callback runs after the
141    /// colon, and reporting from there would name the value instead. See
142    /// [`json::Parser::read_map_located`](crate::json::Parser::read_map_located).
143    ///
144    /// The name is not carried, an [`ErrorCode`] being one byte and the name
145    /// being a run of the document rather than a constant of the destination.
146    /// [`Error::key_in`](crate::Error::key_in) reads it back out of the
147    /// document, which is where it still is.
148    UnknownKey,
149    /// An enum's tag named no variant the destination declares.
150    ///
151    /// Unlike [`UnknownKey`](Self::UnknownKey) this is refused under every
152    /// policy, [`SkipUnknown`](crate::SkipUnknown) included. A member with
153    /// nowhere to go can be stepped over and the rest of the object still
154    /// read; a variant with nowhere to go leaves the value itself undecided.
155    UnknownVariant,
156    /// An object left out a field that had to be there: one marked
157    /// `#[required]` in the declaration, or any of them under
158    /// [`Options::ERROR_ON_MISSING_KEYS`](crate::Options::ERROR_ON_MISSING_KEYS).
159    ///
160    /// Neither is on by default: absence otherwise means the destination keeps
161    /// what it already held. Mark the members a document has to carry, or read
162    /// with [`RequireKeys`](crate::RequireKeys) to insist on every one.
163    ///
164    /// Which field is missing is not carried, an [`ErrorCode`] being one byte.
165    /// The reported position is where the object began, its opening brace in
166    /// JSON and its header byte in BEVE, so the message names the incomplete
167    /// object rather than the byte that closed it. [`Matrix`](crate::Matrix)
168    /// reads by hand and reports the same way.
169    MissingKey,
170    /// A matrix held a different number of elements than its extents describe.
171    InvalidMatrixShape,
172    /// A matrix named a storage order that is not one of the two defined. It
173    /// says which index varies fastest, so reading it wrongly would transpose
174    /// the data without any length being wrong, which is why it is refused
175    /// rather than guessed at.
176    InvalidMatrixLayout,
177
178    // Pointers
179    /// A pointer was not valid [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901)
180    /// syntax: it did not start with `/`, or a token held a stray `~`, or an
181    /// array index was not a decimal number without leading zeros. The `-` the
182    /// RFC defines for the position after the last element is well formed and
183    /// so is [`NoSuchValue`](Self::NoSuchValue) instead.
184    InvalidPointer,
185    /// A pointer was well formed but named a member or element the document
186    /// does not hold.
187    NoSuchValue,
188}
189
190impl ErrorCode {
191    /// A short, stable, human readable description.
192    pub const fn message(self) -> &'static str {
193        use ErrorCode::*;
194        match self {
195            UnexpectedEnd => "unexpected end of input",
196            ExpectedBrace => "expected '{'",
197            ExpectedBracket => "expected '['",
198            ExpectedQuote => "expected '\"'",
199            ExpectedColon => "expected ':'",
200            ExpectedComma => "expected ','",
201            ExpectedTrue => "expected 'true'",
202            ExpectedFalse => "expected 'false'",
203            ExpectedNull => "expected 'null'",
204            UnexpectedCharacter => "unexpected character",
205            TrailingContent => "trailing content after value",
206            ExceededMaxDepth => "exceeded maximum nesting depth",
207            DocumentTooLarge => "value exceeds the streaming size limit",
208            InvalidHeader => "invalid BEVE header",
209            InvalidPadding => "invalid padding in a packed boolean or aligned array",
210            UnsupportedFeature => "unsupported BEVE feature",
211            UnsupportedKeyType => "unsupported object key type",
212            ExpectedObject => "expected an object",
213            ExpectedArray => "expected an array",
214            ExpectedString => "expected a string",
215            ExpectedBool => "expected a boolean",
216            ExpectedInteger => "expected an integer",
217            ExpectedBytes => "expected an array of bytes",
218            ElementTypeMismatch => "array element type does not match the target",
219            ExpectedComplex => "expected a complex number",
220            ExpectedMatrix => "expected a matrix",
221            ExpectedVariant => "expected an enum variant",
222            ExpectedTag => "expected the tag as the object's first member",
223            NumberOutOfRange => "number out of range for target type",
224            InvalidNumber => "invalid number",
225            ExpectedNumber => "expected a number",
226            InvalidEscape => "invalid escape sequence",
227            InvalidSurrogate => "invalid surrogate pair",
228            InvalidUtf8 => "invalid UTF-8",
229            ControlCharacterInString => "unescaped control character in string",
230            EscapeInBorrowedString => "cannot borrow a string containing escapes",
231            ArrayLengthMismatch => "array length does not match target",
232            ExpectedSingleChar => "expected a single character",
233            UnknownKey => "unknown object key",
234            UnknownVariant => "unknown enum variant",
235            MissingKey => "missing object key",
236            InvalidMatrixShape => "matrix extents do not describe its data",
237            InvalidMatrixLayout => "unknown matrix layout",
238            InvalidPointer => "invalid JSON Pointer",
239            NoSuchValue => "no value at that pointer",
240        }
241    }
242}
243
244impl fmt::Display for ErrorCode {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        f.write_str(self.message())
247    }
248}
249
250/// A code stands on its own wherever there is no input to locate it in, which
251/// is why [`Matrix::new`](crate::Matrix::new) hands one back rather than an
252/// [`Error`]: a value assembled in memory has no byte offset to report.
253impl std::error::Error for ErrorCode {}
254
255/// A parse or serialization failure, located within the input.
256///
257/// The location is an *offset*, not a copy of anything: it indexes the buffer
258/// you handed the parser, and it means nothing once that buffer is gone. A
259/// caller that converts this into its own error type and drops the input
260/// should render it first, with [`display_with`](Self::display_with), and
261/// carry the `String`. See [`docs/errors.md`] for the shape that has.
262///
263/// This is the currency of the public entry points. The trait methods speak the
264/// other one, [`ErrorCode`] alone, the offset being attached once at the entry
265/// point from the cursor that stopped. An impl that composes an entry point
266/// into itself, as [`Raw`](crate::json::Raw) composes
267/// [`minify_with`](crate::json::minify_with), passes the code up and drops the
268/// offset: it is a position in the span that call was handed, and a position in
269/// a span names the wrong byte of the document the span came out of. That seam
270/// is deliberate, and [`docs/errors.md`] has why.
271///
272/// [`docs/errors.md`]: https://github.com/matrix-research-inc/structio/blob/main/docs/errors.md
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub struct Error {
275    /// What went wrong.
276    pub code: ErrorCode,
277    /// Byte offset into the input where the failure was detected.
278    pub index: usize,
279    /// The key the code is about, where the crate knows one, and `None` where
280    /// it does not.
281    ///
282    /// The offset answers "where", and for a text document read next to
283    /// [`display_with`](Self::display_with) that is usually the whole answer.
284    /// It is a poor answer for a member that is *not in the document*, which
285    /// has no position of its own, and a thin answer for BEVE, a byte offset
286    /// into a binary document being nearly unreadable. So the one code that
287    /// fills this in is [`MissingKey`](ErrorCode::MissingKey), whose offset is
288    /// the enclosing object's first byte and whose name is the absent key.
289    ///
290    /// Every other code leaves it empty, [`UnknownKey`](ErrorCode::UnknownKey)
291    /// and [`UnknownVariant`](ErrorCode::UnknownVariant) included: the cursor
292    /// is already wound back to the offending name, so the offset points at it
293    /// and a copy here would say the same thing twice. Those two are the codes
294    /// [`key_in`](Self::key_in) exists for, being the ones whose name is in
295    /// the document rather than in the schema.
296    ///
297    /// `&'static str`, so an `Error` still outlives the document and stays
298    /// `Copy`. Everything nameable here is a constant of the destination type
299    /// rather than a run of input bytes, which is what makes that possible.
300    ///
301    /// [`Option`] rather than an empty string, which would cost nothing here
302    /// -- the two are the same 32 bytes, the reference being niche-packed --
303    /// and would conflate two different answers. An empty key is legal JSON,
304    /// so `#[required] "" => field` is a schema whose missing key really is
305    /// named `""`, and `Some("")` says that where `""` alone could not.
306    ///
307    /// A hand-written reader sets it with [`json::Parser::set_error_key`] or
308    /// [`beve::Reader::set_error_key`].
309    ///
310    /// ```
311    /// use structio::ErrorCode;
312    ///
313    /// #[derive(Debug, Default)]
314    /// struct Accessor { byte_offset: u32, component_type: u32 }
315    ///
316    /// structio::object!(Accessor as "camelCase" {
317    ///     #[required] byte_offset,
318    ///     #[required] "type" => component_type,
319    /// });
320    ///
321    /// let e = structio::from_str::<Accessor>(r#"{"type":5}"#).unwrap_err();
322    /// assert_eq!(e.code, ErrorCode::MissingKey);
323    /// // The key the document uses, not the Rust field.
324    /// assert_eq!(e.key, Some("byteOffset"));
325    /// // And the offset is the object, which is all an absent member has.
326    /// assert_eq!(e.to_string(), r#"missing object key "byteOffset" at byte 0"#);
327    /// ```
328    ///
329    /// [`json::Parser::set_error_key`]: crate::json::Parser::set_error_key
330    /// [`beve::Reader::set_error_key`]: crate::beve::Reader::set_error_key
331    pub key: Option<&'static str>,
332}
333
334impl Error {
335    /// A failure at an offset, about no key in particular.
336    #[inline]
337    pub const fn new(code: ErrorCode, index: usize) -> Self {
338        Self {
339            code,
340            index,
341            key: None,
342        }
343    }
344
345    /// A failure at an offset, about a key the reader named.
346    ///
347    /// [`Option`] rather than `&'static str`, because this is what an entry
348    /// point composes with [`json::Parser::error_key`] and its BEVE twin,
349    /// which have a key only sometimes. See [`key`](Self::key).
350    ///
351    /// [`json::Parser::error_key`]: crate::json::Parser::error_key
352    #[inline]
353    pub const fn with_key(code: ErrorCode, index: usize, key: Option<&'static str>) -> Self {
354        Self { code, index, key }
355    }
356
357    /// What went wrong and what it was about, which is the half of a message
358    /// that does not depend on where in the document it happened.
359    ///
360    /// Shared, so [`Display`](fmt::Display) and
361    /// [`display_with`](Self::display_with) cannot come to describe the same
362    /// error differently. `{:?}` quotes the key, which marks where it begins
363    /// and ends.
364    ///
365    /// `dyn` rather than a generic: this is a diagnostic, so one indirect call
366    /// is beneath notice and one copy of the code is worth having.
367    fn described(&self, f: &mut dyn fmt::Write) -> fmt::Result {
368        f.write_str(self.code.message())?;
369        match self.key {
370            None => Ok(()),
371            Some(key) => write!(f, " {key:?}"),
372        }
373    }
374
375    /// The key this failure is about, read out of the document it came from.
376    ///
377    /// [`key`](Self::key) holds a name only when that name is a constant of
378    /// the destination type, which for [`MissingKey`](ErrorCode::MissingKey)
379    /// it is. The codes about a name the *document* chose leave it empty and
380    /// wind the cursor back instead, so what they carry is an offset. This
381    /// reads the name back from that offset, and answers for both kinds:
382    ///
383    /// ```
384    /// use structio::ErrorCode;
385    ///
386    /// #[derive(Debug, Default)]
387    /// struct OnlyA { a: u32 }
388    /// structio::object!(OnlyA { a });
389    ///
390    /// let doc = r#"{"a":1,"nope":2}"#;
391    /// let e = structio::from_str::<OnlyA>(doc).unwrap_err();
392    /// assert_eq!(e.code, ErrorCode::UnknownKey);
393    /// assert_eq!(e.key, None); // no `&'static str` could name it
394    /// assert_eq!(e.key_in(doc).unwrap().as_str(), "nope");
395    /// ```
396    ///
397    /// The lifetime is the document's, not this error's, which is what keeps
398    /// [`Error`] `Copy` and independent of the buffer it describes. A key with
399    /// escapes is unescaped, so this allocates exactly when
400    /// [`read_string_body`] would; `"no\u0070e"` comes back as `nope`.
401    ///
402    /// **The document must be the one the offset indexes.** Hand it another
403    /// and the answer is a name from that one, or nonsense, not `None`; the
404    /// offset cannot tell. This is [`display_with`](Self::display_with)'s
405    /// hazard exactly, and worth avoiding the same way, by asking at the parse
406    /// site rather than remembering an offset for later. The one check made is
407    /// that a quote precedes the offset, which every key has: enough to reject
408    /// an offset that names a *value*, as a reader refusing after the colon
409    /// would report, and not enough to tell a key from the text inside a
410    /// string value, which is locally identical to one. A sanity check on a
411    /// diagnostic, not a proof.
412    ///
413    /// JSON only. A BEVE key is not self-delimiting from the byte its offset
414    /// names, its length living in the prefix that offset is already past, so
415    /// a BEVE reader that wants a name takes it from
416    /// [`beve::Reader::read_map_located`] while it is still in hand.
417    ///
418    /// `None` for a code about no key, for an offset with no string at it, and
419    /// for text that is not a well-formed JSON string body. A key set by hand
420    /// with [`set_error_key`] wins over all of this, being the most specific
421    /// answer there is: it was named rather than located.
422    ///
423    /// [`read_string_body`]: crate::json::Parser::read_string_body
424    /// [`set_error_key`]: crate::json::Parser::set_error_key
425    /// [`beve::Reader::read_map_located`]: crate::beve::Reader::read_map_located
426    pub fn key_in<'a>(&self, doc: &'a str) -> Option<crate::json::JsonStr<'a>> {
427        if let Some(key) = self.key {
428            return Some(crate::json::JsonStr::Borrowed(key));
429        }
430        match self.code {
431            ErrorCode::UnknownKey | ErrorCode::UnknownVariant => {
432                // A key is the body of a string, so the byte before it is
433                // the opening quote. One compare, and it rejects the offset a
434                // reader that refused after the colon would carry: a value
435                // begins at its own first byte, which is not preceded by a
436                // quote unless the value is a string, and then the byte named
437                // is the quote itself rather than what follows it. No key sits
438                // at offset 0 either: the shallowest follows a quote that
439                // follows a brace, and a bare variant name follows its own
440                // quote.
441                if *doc.as_bytes().get(self.index.checked_sub(1)?)? != b'"' {
442                    return None;
443                }
444                crate::json::Parser::new(doc.get(self.index..)?)
445                    .read_string_body()
446                    .ok()
447            }
448            _ => None,
449        }
450    }
451
452    /// Render the failure with the surrounding input, for diagnostics.
453    ///
454    /// Shows the line and column plus a caret under the offending byte, and
455    /// the [`key`](Self::key) where there is one.
456    ///
457    /// The input is the one the offset indexes. Hand it a different document
458    /// and you get a caret under an unrelated byte, which is why this is worth
459    /// calling at the parse site rather than remembering the offset for later.
460    pub fn display_with(&self, input: &str) -> String {
461        // `input` is caller-supplied and need not be the document that produced
462        // this error, so the index may land inside a character. A diagnostic
463        // helper must not panic; round down to the nearest boundary.
464        let idx = floor_char_boundary(input, self.index.min(input.len()));
465        // Walk to the start of the offending line.
466        let line_start = input[..idx].rfind('\n').map_or(0, |p| p + 1);
467        let line_end = input[idx..].find('\n').map_or(input.len(), |p| idx + p);
468        let line_no = input[..line_start].bytes().filter(|&b| b == b'\n').count() + 1;
469        let col_no = input[line_start..idx].chars().count() + 1;
470
471        let line = &input[line_start..line_end];
472        // Trim very long lines around the caret so the output stays readable.
473        const WINDOW: usize = 80;
474        let rel = idx - line_start;
475        let (shown, caret_col, elided_left) = if line.len() > WINDOW {
476            let lo = rel.saturating_sub(WINDOW / 2);
477            let lo = floor_char_boundary(line, lo);
478            let hi = ceil_char_boundary(line, (lo + WINDOW).min(line.len()));
479            (&line[lo..hi], rel - lo, lo > 0)
480        } else {
481            (line, rel, false)
482        };
483
484        // The caret is drawn in characters, so convert the byte offset the
485        // slicing produced; otherwise it drifts right on any line holding
486        // multi-byte text, and disagrees with the column just reported.
487        let caret_col = shown[..caret_col].chars().count();
488
489        let mut out = String::new();
490        // `write!` into a `String` cannot fail, and a diagnostic helper is the
491        // last place to propagate an error from.
492        let _ = self.described(&mut out);
493        let _ = writeln!(out, " at line {line_no}, column {col_no}");
494        if elided_left {
495            out.push_str("...");
496        }
497        out.push_str(shown);
498        out.push('\n');
499        if elided_left {
500            out.push_str("   ");
501        }
502        for _ in 0..caret_col {
503            out.push(' ');
504        }
505        out.push('^');
506        out
507    }
508}
509
510// `str::floor_char_boundary` is still unstable, so roll the two we need.
511fn floor_char_boundary(s: &str, mut i: usize) -> usize {
512    while i > 0 && !s.is_char_boundary(i) {
513        i -= 1;
514    }
515    i
516}
517
518fn ceil_char_boundary(s: &str, mut i: usize) -> usize {
519    while i < s.len() && !s.is_char_boundary(i) {
520        i += 1;
521    }
522    i
523}
524
525impl fmt::Display for Error {
526    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527        self.described(f)?;
528        write!(f, " at byte {}", self.index)
529    }
530}
531
532impl std::error::Error for Error {}
533
534/// Result of a public `structio` operation.
535pub type Result<T> = core::result::Result<T, Error>;
536
537/// Result used inside the parser, where the location is implied by the cursor.
538pub(crate) type PResult<T> = core::result::Result<T, ErrorCode>;
539
540// ---------------------------------------------------------------------------
541// Failures that involve the outside world
542// ---------------------------------------------------------------------------
543
544/// A failure from an operation that touches an [`io::Read`] or [`io::Write`].
545///
546/// Reading through a reader, or writing through a sink, has one failure mode
547/// the in-memory API does not: the source or sink itself. [`Error`] is
548/// `Copy + Eq` and [`std::io::Error`] is neither, so the I/O case gets its own
549/// variant here rather than widening it.
550///
551/// [`io::Read`]: std::io::Read
552/// [`io::Write`]: std::io::Write
553#[derive(Debug)]
554#[non_exhaustive]
555pub enum StreamError {
556    /// The underlying reader or writer failed.
557    Io(std::io::Error),
558    /// The bytes were not what was expected.
559    Parse(Error),
560}
561
562/// Result of an operation that can fail on I/O as well as on content.
563pub type StreamResult<T> = core::result::Result<T, StreamError>;
564
565impl StreamError {
566    /// The parse failure, if this was one.
567    pub fn as_parse(&self) -> Option<&Error> {
568        match self {
569            StreamError::Parse(e) => Some(e),
570            StreamError::Io(_) => None,
571        }
572    }
573
574    /// The I/O failure, if this was one.
575    pub fn as_io(&self) -> Option<&std::io::Error> {
576        match self {
577            StreamError::Io(e) => Some(e),
578            StreamError::Parse(_) => None,
579        }
580    }
581}
582
583impl fmt::Display for StreamError {
584    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585        match self {
586            StreamError::Io(e) => write!(f, "i/o error: {e}"),
587            StreamError::Parse(e) => e.fmt(f),
588        }
589    }
590}
591
592impl std::error::Error for StreamError {
593    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
594        match self {
595            StreamError::Io(e) => Some(e),
596            StreamError::Parse(e) => Some(e),
597        }
598    }
599}
600
601impl From<std::io::Error> for StreamError {
602    fn from(e: std::io::Error) -> Self {
603        StreamError::Io(e)
604    }
605}
606
607impl From<Error> for StreamError {
608    fn from(e: Error) -> Self {
609        StreamError::Parse(e)
610    }
611}