epics_base_rs/error.rs
1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum CaError {
5 #[error("I/O error: {0}")]
6 Io(#[from] std::io::Error),
7
8 #[error("timeout waiting for response")]
9 Timeout,
10
11 #[error("channel not found: {0}")]
12 ChannelNotFound(String),
13
14 #[error("protocol error: {0}")]
15 Protocol(String),
16
17 #[error("unsupported DBR type: {0}")]
18 UnsupportedType(u16),
19
20 #[error("write failed: ECA status {0:#06x}")]
21 WriteFailed(u32),
22
23 #[error("field not found: {0}")]
24 FieldNotFound(String),
25
26 #[error("field is read-only: {0}")]
27 ReadOnlyField(String),
28
29 #[error("type mismatch for field {0}")]
30 TypeMismatch(String),
31
32 #[error("invalid value: {0}")]
33 InvalidValue(String),
34
35 /// C `S_db_badField` ("Illegal RECORD FIELD") — a record's `special()`
36 /// refused the value that `dbPut` had already stored, e.g.
37 /// `calcRecord.c:146-151` returning it for an uncompilable `CALC`. The
38 /// value stays written, the field's monitor is not posted, the record is
39 /// not processed, and the status propagates to the client (rsrv
40 /// `write_action` → `ECA_PUTFAIL`).
41 #[error("illegal record field: {0}")]
42 BadField(String),
43
44 /// C `S_db_badChoice` ("Illegal choice") — a `DBR_STRING` write to a
45 /// `DBF_MENU` field named neither an exact choice label nor an in-range
46 /// index (`dbConvert.c::putStringMenu:1216-1229`). C's converter returns
47 /// this from inside `dbPut`, *before* the value is stored
48 /// (`dbAccess.c:1362`), so the field keeps its old value, no monitor is
49 /// posted and the record is not processed.
50 #[error("illegal menu choice: {0}")]
51 BadChoice(String),
52
53 #[error("put disabled (DISP=1) for field {0}")]
54 PutDisabled(String),
55
56 #[error("link error: {0}")]
57 LinkError(String),
58
59 #[error("DB parse error at line {line}, column {column}: {message}")]
60 DbParseError {
61 line: usize,
62 column: usize,
63 message: String,
64 },
65
66 #[error("calc error: {0}")]
67 CalcError(String),
68
69 #[error("channel disconnected")]
70 Disconnected,
71
72 #[error("client shut down")]
73 Shutdown,
74
75 /// CA WRITE_NOTIFY arrived while a previous async put on the same
76 /// record is still in flight. Mirrors C EPICS `S_db_Blocked`; the
77 /// CA server replies `ECA_PUTCBINPROG`.
78 #[error("put callback in progress for record {0}")]
79 PutCallbackInProgress(String),
80
81 /// Server-emitted ECA status carried out-of-band on an otherwise
82 /// data-shaped frame — used by libca `cac::eventAddRespAction`
83 /// (`cac.cpp:973-977`) when a monitor frame's `m_cid` is non-
84 /// NORMAL (e.g. `ECA_NORDACCESS` from `no_read_access_event`
85 /// after an ACF reload). Routed to the per-subscription
86 /// callback as `Err(CaError::ServerError(eca_status))` so the
87 /// subscriber surfaces the status instead of seeing the bogus
88 /// zeroed payload that travels with the frame.
89 #[error("server reported ECA status {0:#06x}")]
90 ServerError(u32),
91
92 /// Request cannot be framed for the peer: it needs the extended
93 /// (24-byte) CA header, and the peer's protocol version predates
94 /// CA_V49, or the element count exceeds what the peer can carry.
95 /// libca raises this locally — `comQueSend::insertRequestHeader`
96 /// throws `cacChannel::outOfBounds()` (`comQueSend.cpp:299,313`)
97 /// and `ca_array_get`/`ca_array_put` return `ECA_TOLARGE` — so no
98 /// byte reaches the wire.
99 #[error("request too large for the peer's CA protocol version (ECA_TOLARGE)")]
100 TooLarge,
101
102 /// Element count out of bounds for the request C would build. libca's
103 /// put path throws `cacChannel::outOfBounds()` for an array that cannot
104 /// fit the peer's message-body limit (`comQueSend.cpp:361`) or that
105 /// needs an extended header the peer cannot parse
106 /// (`comQueSend.cpp:313`); `oldChannelNotify.cpp:309,378,453` map that
107 /// to `ECA_BADCOUNT`. Raised locally — no byte reaches the wire.
108 #[error("element count out of bounds for this CA circuit (ECA_BADCOUNT)")]
109 BadCount,
110}
111
112// ECA status constants (originally from protocol.rs, now in epics-ca-rs)
113const ECA_TIMEOUT: u32 = 80; // defmsg(CA_K_WARNING, 10)
114const ECA_NOWTACCESS: u32 = 376; // defmsg(CA_K_WARNING, 47)
115const ECA_PUTFAIL: u32 = 160; // defmsg(CA_K_WARNING, 20)
116const ECA_BADTYPE: u32 = 114; // defmsg(CA_K_ERROR, 14)
117const ECA_DISCONN: u32 = 192; // defmsg(CA_K_WARNING, 24)
118const ECA_PUTCBINPROG: u32 = 362; // defmsg(CA_K_ERROR, 45) = (45 << 3) | 2
119const ECA_TOLARGE: u32 = 72; // defmsg(CA_K_WARNING, 9)
120const ECA_BADCOUNT: u32 = 176; // defmsg(CA_K_WARNING, 22)
121
122impl CaError {
123 pub fn to_eca_status(&self) -> u32 {
124 match self {
125 CaError::Timeout => ECA_TIMEOUT,
126 CaError::ReadOnlyField(_) => ECA_NOWTACCESS,
127 CaError::PutDisabled(_) => ECA_PUTFAIL,
128 CaError::TypeMismatch(_) => ECA_BADTYPE,
129 CaError::UnsupportedType(_) => ECA_BADTYPE,
130 CaError::InvalidValue(_) => ECA_BADTYPE,
131 CaError::FieldNotFound(_) => ECA_PUTFAIL,
132 // C rsrv answers any non-zero `db_put_field` status — including
133 // `S_db_badField` — with ECA_PUTFAIL (`camessage.c::write_action`).
134 CaError::BadField(_) => ECA_PUTFAIL,
135 // Likewise `S_db_badChoice` from the string→menu converter: the
136 // put-notify path maps any non-`notifyOK` status to ECA_PUTFAIL
137 // (`db_access.c::db_put_process:1041`, `camessage.c:1386`).
138 CaError::BadChoice(_) => ECA_PUTFAIL,
139 // Disconnection / shutdown are surfaced as ECA_DISCONN so a
140 // downstream client (e.g. caput on a CA gateway whose
141 // upstream just dropped) sees the actionable
142 // "CA channel disconnected" message rather than the
143 // catch-all "Put fail".
144 CaError::Disconnected | CaError::Shutdown => ECA_DISCONN,
145 // I/O errors usually mean the upstream connection is
146 // wedged; mapping to ECA_DISCONN matches operator
147 // expectations from C ca-gateway.
148 CaError::Io(_) => ECA_DISCONN,
149 CaError::WriteFailed(code) => *code,
150 CaError::PutCallbackInProgress(_) => ECA_PUTCBINPROG,
151 CaError::ServerError(code) => *code,
152 CaError::TooLarge => ECA_TOLARGE,
153 CaError::BadCount => ECA_BADCOUNT,
154 _ => ECA_PUTFAIL,
155 }
156 }
157}
158
159pub type CaResult<T> = Result<T, CaError>;