1#[cfg(not(feature = "std"))]
8use alloc::{format, 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("message is too long for routed path to DNET {dnet}")]
60 RoutedPathTooLong {
61 dnet: u16,
63 },
64
65 #[error("routed path state capacity of {capacity} entries is exhausted")]
67 RoutedPathCapacityExceeded {
68 capacity: usize,
70 },
71
72 #[error("encoding error: {0}")]
74 Encoding(String),
75
76 #[error("decoding error at offset {offset}: {message}")]
78 Decoding {
79 offset: usize,
81 message: String,
83 },
84
85 #[cfg(feature = "std")]
87 #[error("transport error: {0}")]
88 Transport(#[from] std::io::Error),
89
90 #[cfg(feature = "std")]
92 #[error("request timed out after {0:?}")]
93 Timeout(Duration),
94
95 #[error("segmentation error: {0}")]
97 Segmentation(String),
98
99 #[error("buffer too short: need {need} bytes, have {have}")]
101 BufferTooShort {
102 need: usize,
104 have: usize,
106 },
107
108 #[error("invalid tag: {0}")]
110 InvalidTag(String),
111
112 #[error("value out of range: {0}")]
114 OutOfRange(String),
115}
116
117pub type Result<T> = core::result::Result<T, Error>;
119
120impl Error {
121 pub fn decoding(offset: usize, message: impl Into<String>) -> Self {
123 Self::Decoding {
124 offset,
125 message: message.into(),
126 }
127 }
128
129 pub fn buffer_too_short(need: usize, have: usize) -> Self {
131 Self::BufferTooShort { need, have }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn protocol_error_display() {
141 let err = Error::Protocol { class: 2, code: 31 };
142 assert!(err.to_string().contains("property"));
143 assert!(err.to_string().contains("unknown-object"));
144
145 let err2 = Error::Protocol {
147 class: 999,
148 code: 999,
149 };
150 assert!(err2.to_string().contains("class=999"));
151 assert!(err2.to_string().contains("code=999"));
152 }
153
154 #[test]
155 fn decoding_error_display() {
156 let err = Error::decoding(42, "unexpected tag");
157 assert!(err.to_string().contains("offset 42"));
158 assert!(err.to_string().contains("unexpected tag"));
159 }
160
161 #[test]
162 fn buffer_too_short_display() {
163 let err = Error::buffer_too_short(10, 3);
164 assert!(err.to_string().contains("need 10"));
165 assert!(err.to_string().contains("have 3"));
166 }
167
168 #[cfg(feature = "std")]
169 #[test]
170 fn timeout_error_display() {
171 let err = Error::Timeout(Duration::from_secs(3));
172 assert!(err.to_string().contains("3s"));
173 }
174
175 #[test]
176 fn routed_path_too_long_display_preserves_dnet() {
177 let err = Error::RoutedPathTooLong { dnet: 1234 };
178 assert!(err.to_string().contains("1234"));
179 }
180
181 #[test]
182 fn routed_path_capacity_display_preserves_bound() {
183 let err = Error::RoutedPathCapacityExceeded { capacity: 256 };
184 assert!(err.to_string().contains("256"));
185 }
186}