bnb/builder.rs
1//! Support types for `#[derive(BitsBuilder)]` and `#[bin]`.
2
3use alloc::string::String;
4use core::fmt;
5
6/// The error a generated builder's `build()` returns.
7///
8/// Two cases:
9/// - [`MissingField`](BuilderError::MissingField) — a **required** field was
10/// never set. "Required" is the default; a field is only optional if it carries
11/// `#[builder(default)]` (or `#[builder(default = expr)]`). This is what lets the
12/// builder *call out* an unset bit/byte instead of silently defaulting it to
13/// zero (the gap in the infix `with_*` API).
14/// - [`Invalid`](BuilderError::Invalid) — a `#[bin(validate = …)]` soundness
15/// check rejected the built value. The string is the validator's error,
16/// stringified, so any `Display` error type composes without coupling the
17/// builder to a protocol-specific error type.
18///
19/// # Examples
20///
21/// ```
22/// use bnb::{bitfield, u4, BitsBuilder, BuilderError};
23///
24/// #[bitfield(u8, bits = msb)]
25/// #[derive(BitsBuilder, Clone, Copy, Debug)]
26/// struct Nibbles { hi: u4, lo: u4 }
27///
28/// let err = Nibbles::builder().hi(u4::new(0xA)).build().unwrap_err();
29/// assert_eq!(err, BuilderError::MissingField("lo"));
30/// assert_eq!(err.field(), Some("lo"));
31/// assert_eq!(err.to_string(), "required field `lo` was not set");
32/// ```
33#[derive(Clone, Debug, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum BuilderError {
36 /// A required field was not set; carries the field name.
37 MissingField(&'static str),
38 /// A soundness validator rejected the value; carries its message.
39 Invalid(String),
40}
41
42impl BuilderError {
43 /// Constructs the "required field not set" error for `field`.
44 pub fn missing_field(field: &'static str) -> Self {
45 Self::MissingField(field)
46 }
47
48 /// Constructs a soundness-failure error from a validator's message.
49 pub fn invalid(message: impl Into<String>) -> Self {
50 Self::Invalid(message.into())
51 }
52
53 /// The name of the field that was not set, or `None` for an
54 /// [`Invalid`](BuilderError::Invalid) error.
55 pub fn field(&self) -> Option<&'static str> {
56 match self {
57 Self::MissingField(field) => Some(field),
58 Self::Invalid(_) => None,
59 }
60 }
61}
62
63impl fmt::Display for BuilderError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 Self::MissingField(field) => write!(f, "required field `{field}` was not set"),
67 Self::Invalid(message) => write!(f, "soundness check failed: {message}"),
68 }
69 }
70}
71
72impl core::error::Error for BuilderError {}
73
74#[cfg(test)]
75mod unit {
76 use super::*;
77 use alloc::string::ToString;
78
79 #[test]
80 fn missing_field_constructor_and_accessor() {
81 let e = BuilderError::missing_field("flags");
82 assert_eq!(e, BuilderError::MissingField("flags"));
83 assert_eq!(e.field(), Some("flags"));
84 }
85
86 #[test]
87 fn missing_field_display() {
88 assert_eq!(
89 BuilderError::missing_field("flags").to_string(),
90 "required field `flags` was not set",
91 );
92 }
93
94 #[test]
95 fn invalid_constructor_accepts_any_display() {
96 // `impl Into<String>` — a &str and a String both compose.
97 let from_str = BuilderError::invalid("bad");
98 let from_string = BuilderError::invalid(String::from("bad"));
99 assert_eq!(from_str, from_string);
100 assert_eq!(from_str, BuilderError::Invalid("bad".into()));
101 }
102
103 #[test]
104 fn invalid_has_no_field_name() {
105 assert_eq!(BuilderError::invalid("nope").field(), None);
106 }
107
108 #[test]
109 fn invalid_display_wraps_the_message() {
110 assert_eq!(
111 BuilderError::invalid("kind 0 is reserved").to_string(),
112 "soundness check failed: kind 0 is reserved",
113 );
114 }
115}