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}