1#[cfg(not(feature = "std"))]
8use alloc::string::String;
9#[cfg(feature = "std")]
10use std::time::Duration;
11
12use crate::enums::{ErrorClass, ErrorCode};
13
14fn format_protocol_error(class: u32, code: u32) -> String {
15 let class_name = ErrorClass::ALL_NAMED
16 .iter()
17 .find(|(_, v)| v.to_raw() as u32 == class)
18 .map(|(n, _)| n.to_lowercase().replace('_', "-"));
19 let code_name = ErrorCode::ALL_NAMED
20 .iter()
21 .find(|(_, v)| v.to_raw() as u32 == code)
22 .map(|(n, _)| n.to_lowercase().replace('_', "-"));
23
24 match (class_name, code_name) {
25 (Some(cn), Some(co)) => format!("BACnet error: {cn} / {co}"),
26 (Some(cn), None) => format!("BACnet error: {cn} / code={code}"),
27 (None, Some(co)) => format!("BACnet error: class={class} / {co}"),
28 (None, None) => format!("BACnet error: class={class} / code={code}"),
29 }
30}
31
32#[derive(Debug, thiserror::Error)]
34pub enum Error {
35 #[error("{}", format_protocol_error(*.class, *.code))]
37 Protocol {
38 class: u32,
40 code: u32,
42 },
43
44 #[error("BACnet reject: reason={reason}")]
46 Reject {
47 reason: u8,
49 },
50
51 #[error("BACnet abort: reason={reason}")]
53 Abort {
54 reason: u8,
56 },
57
58 #[error("encoding error: {0}")]
60 Encoding(String),
61
62 #[error("decoding error at offset {offset}: {message}")]
64 Decoding {
65 offset: usize,
67 message: String,
69 },
70
71 #[cfg(feature = "std")]
73 #[error("transport error: {0}")]
74 Transport(#[from] std::io::Error),
75
76 #[cfg(feature = "std")]
78 #[error("request timed out after {0:?}")]
79 Timeout(Duration),
80
81 #[error("segmentation error: {0}")]
83 Segmentation(String),
84
85 #[error("buffer too short: need {need} bytes, have {have}")]
87 BufferTooShort {
88 need: usize,
90 have: usize,
92 },
93
94 #[error("invalid tag: {0}")]
96 InvalidTag(String),
97
98 #[error("value out of range: {0}")]
100 OutOfRange(String),
101}
102
103pub type Result<T> = core::result::Result<T, Error>;
105
106impl Error {
107 pub fn decoding(offset: usize, message: impl Into<String>) -> Self {
109 Self::Decoding {
110 offset,
111 message: message.into(),
112 }
113 }
114
115 pub fn buffer_too_short(need: usize, have: usize) -> Self {
117 Self::BufferTooShort { need, have }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn protocol_error_display() {
127 let err = Error::Protocol { class: 2, code: 31 };
128 assert!(err.to_string().contains("property"));
129 assert!(err.to_string().contains("unknown-object"));
130
131 let err2 = Error::Protocol {
133 class: 999,
134 code: 999,
135 };
136 assert!(err2.to_string().contains("class=999"));
137 assert!(err2.to_string().contains("code=999"));
138 }
139
140 #[test]
141 fn decoding_error_display() {
142 let err = Error::decoding(42, "unexpected tag");
143 assert!(err.to_string().contains("offset 42"));
144 assert!(err.to_string().contains("unexpected tag"));
145 }
146
147 #[test]
148 fn buffer_too_short_display() {
149 let err = Error::buffer_too_short(10, 3);
150 assert!(err.to_string().contains("need 10"));
151 assert!(err.to_string().contains("have 3"));
152 }
153
154 #[cfg(feature = "std")]
155 #[test]
156 fn timeout_error_display() {
157 let err = Error::Timeout(Duration::from_secs(3));
158 assert!(err.to_string().contains("3s"));
159 }
160}