Skip to main content

bacnet_rs/client/
error.rs

1//! Error types for the high-level BACnet client.
2//!
3//! The client returns a single, typed [`ClientError`] from all of its public
4//! methods. This replaces the previous `Box<dyn std::error::Error>` returns and
5//! lets callers match on specific failure modes (timeouts, protocol-level
6//! rejects/aborts, per-property errors, etc.) instead of inspecting strings.
7
8use crate::encoding::EncodingError;
9use crate::service::{AbortReason, RejectReason};
10use thiserror::Error;
11
12/// Errors that can occur while using the high-level [`BacnetClient`](super::BacnetClient).
13#[derive(Debug, Error)]
14pub enum ClientError {
15    /// An underlying socket / I/O operation failed.
16    #[error("I/O error: {0}")]
17    Io(#[from] std::io::Error),
18
19    /// A request could not be encoded, or a response could not be decoded,
20    /// using the BACnet encoding rules.
21    #[error("encoding error: {0}")]
22    Encoding(#[from] EncodingError),
23
24    /// A response was malformed or could not be interpreted.
25    #[error("failed to decode response: {0}")]
26    Decode(String),
27
28    /// No response was received within the configured timeout.
29    #[error("request timed out")]
30    Timeout,
31
32    /// A response was expected but the peer returned nothing usable.
33    #[error("no response from device")]
34    NoResponse,
35
36    /// The remote device rejected the request at the application layer.
37    #[error("request rejected: {0}")]
38    Rejected(RejectReason),
39
40    /// The remote device aborted the transaction.
41    #[error("transaction aborted: {0}")]
42    Abort(AbortReason),
43
44    /// The device returned a BACnet `Error` PDU (or a per-property error inside
45    /// a ReadPropertyMultiple result), identified by its error class and code.
46    #[error("{}", describe_bacnet_error(*class, *code))]
47    PropertyError {
48        /// BACnet error class.
49        class: u32,
50        /// BACnet error code.
51        code: u32,
52    },
53
54    /// A supplied address could not be parsed or resolved.
55    #[error("invalid address: {0}")]
56    AddressParse(String),
57}
58
59/// Human-readable name for a BACnet error class (ASHRAE 135 `BACnetErrorClass`).
60fn error_class_name(class: u32) -> Option<&'static str> {
61    Some(match class {
62        0 => "device",
63        1 => "object",
64        2 => "property",
65        3 => "resources",
66        4 => "security",
67        5 => "services",
68        6 => "vt",
69        7 => "communication",
70        _ => return None,
71    })
72}
73
74/// Human-readable name for the common BACnet error codes (ASHRAE 135
75/// `BACnetErrorCode`). Not exhaustive — unknown codes fall back to the number.
76fn error_code_name(code: u32) -> Option<&'static str> {
77    Some(match code {
78        0 => "other",
79        9 => "invalid-data-type",
80        20 => "no-space-to-write-property",
81        23 => "object-deletion-not-permitted",
82        27 => "read-access-denied",
83        29 => "service-request-denied",
84        30 => "timeout",
85        31 => "unknown-object",
86        32 => "unknown-property",
87        37 => "value-out-of-range",
88        40 => "write-access-denied",
89        44 => "not-cov-property",
90        45 => "optional-functionality-not-supported",
91        47 => "datatype-not-supported",
92        50 => "property-is-not-an-array",
93        70 => "unknown-device",
94        _ => return None,
95    })
96}
97
98/// Render a BACnet error class/code pair, naming both where known and always
99/// including the raw numbers, e.g. `write-access-denied (class property[2], code 40)`.
100fn describe_bacnet_error(class: u32, code: u32) -> String {
101    let class_label = match error_class_name(class) {
102        Some(name) => format!("{name}[{class}]"),
103        None => format!("{class}"),
104    };
105    match error_code_name(code) {
106        Some(name) => format!("{name} (class {class_label}, code {code})"),
107        None => format!("BACnet error (class {class_label}, code {code})"),
108    }
109}