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}