Skip to main content

bacnet_types/
error.rs

1//! BACnet error types.
2//!
3//! Provides the top-level [`Error`] type used throughout the library,
4//! covering protocol errors, encoding/decoding failures, transport issues,
5//! and timeouts.
6
7#[cfg(not(feature = "std"))]
8use alloc::string::String;
9#[cfg(feature = "std")]
10use std::time::Duration;
11
12/// Top-level error type for the BACnet library.
13#[derive(Debug, thiserror::Error)]
14pub enum Error {
15    /// BACnet protocol error response (Clause 20.1.7).
16    #[error("BACnet error: class={class} code={code}")]
17    Protocol {
18        /// Error class value.
19        class: u32,
20        /// Error code value.
21        code: u32,
22    },
23
24    /// BACnet reject PDU (Clause 20.1.5).
25    #[error("BACnet reject: reason={reason}")]
26    Reject {
27        /// Reject reason value.
28        reason: u8,
29    },
30
31    /// BACnet abort PDU (Clause 20.1.6).
32    #[error("BACnet abort: reason={reason}")]
33    Abort {
34        /// Abort reason value.
35        reason: u8,
36    },
37
38    /// Error encoding a PDU.
39    #[error("encoding error: {0}")]
40    Encoding(String),
41
42    /// Error decoding received data.
43    #[error("decoding error at offset {offset}: {message}")]
44    Decoding {
45        /// Byte offset where the error occurred.
46        offset: usize,
47        /// Description of what went wrong.
48        message: String,
49    },
50
51    /// Transport-level I/O error.
52    #[cfg(feature = "std")]
53    #[error("transport error: {0}")]
54    Transport(#[from] std::io::Error),
55
56    /// Request timed out.
57    #[cfg(feature = "std")]
58    #[error("request timed out after {0:?}")]
59    Timeout(Duration),
60
61    /// Segmentation assembly error.
62    #[error("segmentation error: {0}")]
63    Segmentation(String),
64
65    /// Buffer too short for the expected data.
66    #[error("buffer too short: need {need} bytes, have {have}")]
67    BufferTooShort {
68        /// Minimum bytes needed.
69        need: usize,
70        /// Bytes actually available.
71        have: usize,
72    },
73
74    /// Invalid tag encountered during decode.
75    #[error("invalid tag: {0}")]
76    InvalidTag(String),
77
78    /// Value out of valid range.
79    #[error("value out of range: {0}")]
80    OutOfRange(String),
81}
82
83/// Convenience alias for `Result<T, Error>`.
84pub type Result<T> = core::result::Result<T, Error>;
85
86impl Error {
87    /// Create a decoding error at the given byte offset.
88    pub fn decoding(offset: usize, message: impl Into<String>) -> Self {
89        Self::Decoding {
90            offset,
91            message: message.into(),
92        }
93    }
94
95    /// Create a buffer-too-short error.
96    pub fn buffer_too_short(need: usize, have: usize) -> Self {
97        Self::BufferTooShort { need, have }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn protocol_error_display() {
107        let err = Error::Protocol { class: 2, code: 31 };
108        assert!(err.to_string().contains("class=2"));
109        assert!(err.to_string().contains("code=31"));
110    }
111
112    #[test]
113    fn decoding_error_display() {
114        let err = Error::decoding(42, "unexpected tag");
115        assert!(err.to_string().contains("offset 42"));
116        assert!(err.to_string().contains("unexpected tag"));
117    }
118
119    #[test]
120    fn buffer_too_short_display() {
121        let err = Error::buffer_too_short(10, 3);
122        assert!(err.to_string().contains("need 10"));
123        assert!(err.to_string().contains("have 3"));
124    }
125
126    #[cfg(feature = "std")]
127    #[test]
128    fn timeout_error_display() {
129        let err = Error::Timeout(Duration::from_secs(3));
130        assert!(err.to_string().contains("3s"));
131    }
132}