Skip to main content

bacnet_types/error/
remote.rs

1//! [`RemoteError`]: how a peer turned a confirmed request down.
2
3use core::fmt;
4
5use crate::enums::{AbortReason, ErrorClass, ErrorCode, RejectReason};
6
7/// How a peer turned a confirmed request down: an Error PDU's class and
8/// code, or a Reject or Abort PDU's reason (Clauses 20.1.5 to 20.1.7).
9///
10/// One value carries the answer from the wire to the API (#1815). The client
11/// TSM completes a transaction with it, [`Error::Remote`](super::Error::Remote)
12/// reports it, and a server keeps it as the answer to a request it sent. An
13/// Error PDU whose Clause 21 production adds fields beyond the class and code
14/// is `Error::Structured` (with `alloc`) instead.
15///
16/// It displays as the [`Error`](super::Error) that holds it does: an error's
17/// class and code by name in lower case with hyphens (the number for a value
18/// without a name), a reason as its number.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
20pub enum RemoteError {
21    /// An Error PDU (Clause 20.1.7).
22    #[error("{}", ProtocolText { class: *class, code: *code })]
23    Error {
24        /// Error class (Clause 18).
25        class: ErrorClass,
26        /// Error code (Clause 18).
27        code: ErrorCode,
28    },
29    /// A Reject PDU (Clause 20.1.5).
30    #[error("BACnet reject: reason={}", reason.to_raw())]
31    Reject {
32        /// Reject reason (Clause 18.9).
33        reason: RejectReason,
34    },
35    /// An Abort PDU (Clause 20.1.6).
36    #[error("BACnet abort: reason={}", reason.to_raw())]
37    Abort {
38        /// Abort reason (Clause 18.10).
39        reason: AbortReason,
40    },
41}
42
43/// How a protocol error's class and code read: each name in lower case with
44/// hyphens, or the raw number for a value without one.
45pub(super) struct ProtocolText {
46    pub(super) class: ErrorClass,
47    pub(super) code: ErrorCode,
48}
49
50impl fmt::Display for ProtocolText {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        fn name(
53            f: &mut fmt::Formatter<'_>,
54            named: Option<&str>,
55            field: &str,
56            value: u16,
57        ) -> fmt::Result {
58            let Some(named) = named else {
59                return write!(f, "{field}={value}");
60            };
61            for c in named.chars() {
62                let c = if c == '_' {
63                    '-'
64                } else {
65                    c.to_ascii_lowercase()
66                };
67                fmt::Write::write_char(f, c)?;
68            }
69            Ok(())
70        }
71        f.write_str("BACnet error: ")?;
72        name(f, self.class.name(), "class", self.class.to_raw())?;
73        f.write_str(" / ")?;
74        name(f, self.code.name(), "code", self.code.to_raw())
75    }
76}