bnb/error.rs
1//! The crate's checked-construction error types.
2//!
3//! Hand-rolled (no `thiserror`) to keep `bnb` dependency-light — protocol crates
4//! depend on `bnb` *instead of* a stack of external helpers, so `bnb` itself
5//! stays lean.
6
7use core::fmt;
8
9/// Errors from checked construction.
10///
11/// Currently the only fallible operation is `UInt::try_new` (and the `TryFrom`
12/// impls built on it). Decoding never *rejects an out-of-range value*: the codec is
13/// dual-use, so unknown values are preserved via a `#[catch_all]` variant rather than
14/// rejected, and field access masks rather than validates. (Structural decode failures —
15/// EOF, a bad `magic`, an `assert`, invalid UTF-8 — are the separate
16/// [`BitError`](crate::BitError), not a `WidthError`.)
17///
18/// # Examples
19///
20/// ```
21/// use bnb::{u4, WidthError};
22///
23/// let err = u4::try_new(20).unwrap_err(); // 20 doesn't fit in 4 bits
24/// assert_eq!(err, WidthError::ValueTooLarge { value: 20, bits: 4 });
25/// assert_eq!(err.to_string(), "value 20 does not fit in 4 bits");
26/// ```
27#[derive(Clone, Debug, PartialEq, Eq)]
28#[non_exhaustive]
29pub enum WidthError {
30 /// A value did not fit in the target integer's bit width.
31 ValueTooLarge {
32 /// The offending value.
33 value: u128,
34 /// The maximum number of bits available.
35 bits: u32,
36 },
37}
38
39impl fmt::Display for WidthError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 WidthError::ValueTooLarge { value, bits } => {
43 write!(f, "value {value} does not fit in {bits} bits")
44 }
45 }
46 }
47}
48
49impl core::error::Error for WidthError {}
50
51/// The error returned by the `TryFrom<uN>` impl that `#[derive(BitEnum)]`
52/// generates for a **primitive-width enum without a `#[catch_all]` variant**
53/// (width `u8`/`u16`/`u32`/`u64`/`u128`): the value matched no known discriminant.
54///
55/// A catch-all enum is total, so its primitive→enum conversion is an infallible
56/// `From` and never produces this. This mirrors `num_enum`'s
57/// `TryFromPrimitiveError`. Decoding through the codec / `Bits::from_bits` is
58/// unaffected — that path stays permissive (dual-use); this is only for the
59/// caller who opts into a checked conversion.
60///
61/// # Examples
62///
63/// ```
64/// #[derive(bnb::BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
65/// #[bit_enum(u8, closed)]
66/// #[repr(u8)]
67/// enum Direction { Request = 1, Reply = 2 }
68///
69/// let err = Direction::try_from(9u8).unwrap_err(); // no variant for 9
70/// assert_eq!(err.value, 9);
71/// assert_eq!(err.type_name, "Direction");
72/// assert_eq!(err.to_string(), "Direction has no variant for discriminant 9");
73/// ```
74#[derive(Clone, Debug, PartialEq, Eq)]
75#[non_exhaustive]
76pub struct UnknownDiscriminant {
77 /// The unrecognized value.
78 pub value: u128,
79 /// The name of the enum that rejected it.
80 pub type_name: &'static str,
81}
82
83impl UnknownDiscriminant {
84 /// Builds the error for a `value` that matched no discriminant of the enum
85 /// named `type_name`. Called by `#[derive(BitEnum)]`-generated `TryFrom` impls.
86 #[must_use]
87 pub const fn new(value: u128, type_name: &'static str) -> Self {
88 Self { value, type_name }
89 }
90}
91
92impl fmt::Display for UnknownDiscriminant {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(
95 f,
96 "{} has no variant for discriminant {}",
97 self.type_name, self.value
98 )
99 }
100}
101
102impl core::error::Error for UnknownDiscriminant {}
103
104#[cfg(test)]
105mod unit {
106 use super::*;
107 use alloc::string::ToString;
108
109 #[test]
110 fn value_too_large_carries_value_and_width() {
111 let e = WidthError::ValueTooLarge { value: 20, bits: 4 };
112 assert_eq!(e, WidthError::ValueTooLarge { value: 20, bits: 4 });
113 }
114
115 #[test]
116 fn value_too_large_display() {
117 let e = WidthError::ValueTooLarge { value: 20, bits: 4 };
118 assert_eq!(e.to_string(), "value 20 does not fit in 4 bits");
119 }
120
121 #[test]
122 fn unknown_discriminant_carries_value_and_name() {
123 let e = UnknownDiscriminant {
124 value: 9,
125 type_name: "Direction",
126 };
127 assert_eq!(e.value, 9);
128 assert_eq!(e.type_name, "Direction");
129 }
130
131 #[test]
132 fn unknown_discriminant_display() {
133 let e = UnknownDiscriminant {
134 value: 9,
135 type_name: "Direction",
136 };
137 assert_eq!(e.to_string(), "Direction has no variant for discriminant 9");
138 }
139}