#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(feature = "std")]
use std::time::Duration;
use crate::enums::{ErrorClass, ErrorCode};
fn format_protocol_error(class: u32, code: u32) -> String {
let class_name = ErrorClass::ALL_NAMED
.iter()
.find(|(_, v)| v.to_raw() as u32 == class)
.map(|(n, _)| n.to_lowercase().replace('_', "-"));
let code_name = ErrorCode::ALL_NAMED
.iter()
.find(|(_, v)| v.to_raw() as u32 == code)
.map(|(n, _)| n.to_lowercase().replace('_', "-"));
match (class_name, code_name) {
(Some(cn), Some(co)) => format!("BACnet error: {cn} / {co}"),
(Some(cn), None) => format!("BACnet error: {cn} / code={code}"),
(None, Some(co)) => format!("BACnet error: class={class} / {co}"),
(None, None) => format!("BACnet error: class={class} / code={code}"),
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{}", format_protocol_error(*.class, *.code))]
Protocol {
class: u32,
code: u32,
},
#[error("BACnet reject: reason={reason}")]
Reject {
reason: u8,
},
#[error("BACnet abort: reason={reason}")]
Abort {
reason: u8,
},
#[error("encoding error: {0}")]
Encoding(String),
#[error("decoding error at offset {offset}: {message}")]
Decoding {
offset: usize,
message: String,
},
#[cfg(feature = "std")]
#[error("transport error: {0}")]
Transport(#[from] std::io::Error),
#[cfg(feature = "std")]
#[error("request timed out after {0:?}")]
Timeout(Duration),
#[error("segmentation error: {0}")]
Segmentation(String),
#[error("buffer too short: need {need} bytes, have {have}")]
BufferTooShort {
need: usize,
have: usize,
},
#[error("invalid tag: {0}")]
InvalidTag(String),
#[error("value out of range: {0}")]
OutOfRange(String),
}
pub type Result<T> = core::result::Result<T, Error>;
impl Error {
pub fn decoding(offset: usize, message: impl Into<String>) -> Self {
Self::Decoding {
offset,
message: message.into(),
}
}
pub fn buffer_too_short(need: usize, have: usize) -> Self {
Self::BufferTooShort { need, have }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn protocol_error_display() {
let err = Error::Protocol { class: 2, code: 31 };
assert!(err.to_string().contains("property"));
assert!(err.to_string().contains("unknown-object"));
let err2 = Error::Protocol {
class: 999,
code: 999,
};
assert!(err2.to_string().contains("class=999"));
assert!(err2.to_string().contains("code=999"));
}
#[test]
fn decoding_error_display() {
let err = Error::decoding(42, "unexpected tag");
assert!(err.to_string().contains("offset 42"));
assert!(err.to_string().contains("unexpected tag"));
}
#[test]
fn buffer_too_short_display() {
let err = Error::buffer_too_short(10, 3);
assert!(err.to_string().contains("need 10"));
assert!(err.to_string().contains("have 3"));
}
#[cfg(feature = "std")]
#[test]
fn timeout_error_display() {
let err = Error::Timeout(Duration::from_secs(3));
assert!(err.to_string().contains("3s"));
}
}