bnb/guide/errors.rs
1//! Errors — position-aware, and the streaming `Incomplete` signal.
2//!
3//! The codec's error type is [`BitError`](crate::BitError): a [`kind`](crate::ErrorKind),
4//! the absolute **bit offset** `at` where it happened, and the **field** being
5//! processed when it did (the innermost one wins, like a span). That makes a failure
6//! point at the exact place rather than just "parse error".
7//!
8//! ```
9//! use bnb::{bin, ErrorKind};
10//!
11//! #[bin(big)]
12//! #[derive(Debug)]
13//! struct Header { a: u16, b: u16 }
14//!
15//! // Only one byte: reading `a` (needs 16 bits) runs off the end at bit 0.
16//! let err = Header::decode_exact(&[0x00]).unwrap_err();
17//! assert!(matches!(err.kind, ErrorKind::UnexpectedEof { needed: 16, remaining: 8 }));
18//! assert_eq!(err.at, 0);
19//! assert_eq!(err.field, Some("a")); // the field gives the span
20//! ```
21//!
22//! The [`Display`](std::fmt::Display) impl renders all of it:
23//!
24//! ```
25//! # use bnb::bin;
26//! # #[bin(big)] #[derive(Debug)] struct Header { a: u16, b: u16 }
27//! let err = Header::decode_exact(&[0x00]).unwrap_err();
28//! assert_eq!(
29//! err.to_string(),
30//! "unexpected end of input: needed 16 bits, 8 remain at bit 0 (field `a`)",
31//! );
32//! ```
33//!
34//! # The variants you'll see
35//!
36//! [`ErrorKind`](crate::ErrorKind) is non-exhaustive; the common ones:
37//!
38//! - `UnexpectedEof { needed, remaining }` — ran off the end of a finite slice.
39//! - `TrailingBytes { remaining }` — `decode_exact` left whole bytes unconsumed.
40//! - `BadMagic { expected, found }` — a `magic` constant didn't match.
41//! - `Convert { message }` — a conversion or guard rejected the value: a `try_map` /
42//! `try_wire` converter, a `#[br(assert(...))]` guard, a `WireLen` / `count_prefix`
43//! length that overflowed its prefix type, invalid UTF-8 in a string field, or a
44//! `WidthError` bridged in from checked construction (`try_new`).
45//! - `Incomplete { needed }` — a stream ran out mid-message (read more and retry).
46//! - `NotSeekable` / `BufferFull` / `TooWide` / `Io` — seek-on-a-stream, buffer cap,
47//! over-128-bit field, and an I/O failure (carrying just the [`std::io::ErrorKind`], not
48//! the full `io::Error`). `NotSeekable`/`BufferFull` are exactly what you hit moving from
49//! slices to streams (a `restore_position` on a forward stream; a message larger than a
50//! [`BufSource`](crate::BufSource) cap).
51//!
52//! ```
53//! use bnb::{bin, ErrorKind};
54//!
55//! #[bin(big)]
56//! #[derive(Debug)]
57//! struct One { v: u8 }
58//!
59//! #[bin(big, magic = 0xCAFEu16)]
60//! #[derive(Debug)]
61//! struct Framed { v: u8 }
62//!
63//! // Trailing bytes after a complete message.
64//! let err = One::decode_exact(&[0x01, 0x02]).unwrap_err();
65//! assert!(matches!(err.kind, ErrorKind::TrailingBytes { remaining: 1 }));
66//!
67//! // A magic mismatch points at the magic.
68//! let err = Framed::decode_exact(&[0x00, 0x00, 0x00]).unwrap_err();
69//! assert!(matches!(err.kind, ErrorKind::BadMagic { expected: 0xCAFE, found: 0x0000 }));
70//! assert_eq!(err.field, Some("magic"));
71//! ```
72//!
73//! # Streaming: `Incomplete` means "retry", not "fail"
74//!
75//! When a forward stream runs out partway through a message, the error is
76//! `Incomplete` — a signal to read more bytes and retry the decode, as opposed to a
77//! definitive failure. [`is_incomplete`](crate::BitError::is_incomplete) distinguishes
78//! the two:
79//!
80//! ```
81//! use bnb::{bin, StreamBitReader};
82//!
83//! #[bin(big)]
84//! #[derive(Debug)]
85//! struct Quad { v: u32 }
86//!
87//! // Only 2 of the 4 needed bytes are available so far.
88//! let mut s = StreamBitReader::new(&[0x12, 0x34][..]);
89//! let err = Quad::decode(&mut s).unwrap_err();
90//! assert!(err.is_incomplete()); // buffer more and try again — not a parse error
91//! ```
92//!
93//! # Two error types
94//!
95//! Decoding/encoding yields [`BitError`](crate::BitError). The separate
96//! [`WidthError`](crate::WidthError) covers *construction* (`UInt::try_new` and the `TryFrom`
97//! impls). A `From<WidthError> for BitError` bridges them, so a construction failure inside
98//! a custom `parse_with`/converter can `?`-propagate:
99//!
100//! ```
101//! use bnb::{u4, BitError};
102//! fn make(raw: u8) -> Result<u4, BitError> {
103//! let v = u4::try_new(raw)?; // construction Error -> BitError via `?`
104//! Ok(v)
105//! }
106//! assert!(make(3).is_ok());
107//! assert!(make(99).is_err());
108//! ```