Skip to main content

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    #[error("put disabled (DISP=1) for field {0}")]
36    PutDisabled(String),
37
38    #[error("link error: {0}")]
39    LinkError(String),
40
41    #[error("DB parse error at line {line}, column {column}: {message}")]
42    DbParseError {
43        line: usize,
44        column: usize,
45        message: String,
46    },
47
48    #[error("calc error: {0}")]
49    CalcError(String),
50
51    #[error("channel disconnected")]
52    Disconnected,
53
54    #[error("client shut down")]
55    Shutdown,
56
57    /// CA WRITE_NOTIFY arrived while a previous async put on the same
58    /// record is still in flight. Mirrors C EPICS `S_db_Blocked`; the
59    /// CA server replies `ECA_PUTCBINPROG`.
60    #[error("put callback in progress for record {0}")]
61    PutCallbackInProgress(String),
62
63    /// Server-emitted ECA status carried out-of-band on an otherwise
64    /// data-shaped frame — used by libca `cac::eventAddRespAction`
65    /// (`cac.cpp:973-977`) when a monitor frame's `m_cid` is non-
66    /// NORMAL (e.g. `ECA_NORDACCESS` from `no_read_access_event`
67    /// after an ACF reload). Routed to the per-subscription
68    /// callback as `Err(CaError::ServerError(eca_status))` so the
69    /// subscriber surfaces the status instead of seeing the bogus
70    /// zeroed payload that travels with the frame.
71    #[error("server reported ECA status {0:#06x}")]
72    ServerError(u32),
73}
74
75// ECA status constants (originally from protocol.rs, now in epics-ca-rs)
76const ECA_TIMEOUT: u32 = 80; // defmsg(CA_K_WARNING, 10)
77const ECA_NOWTACCESS: u32 = 376; // defmsg(CA_K_WARNING, 47)
78const ECA_PUTFAIL: u32 = 160; // defmsg(CA_K_WARNING, 20)
79const ECA_BADTYPE: u32 = 114; // defmsg(CA_K_ERROR, 14)
80const ECA_DISCONN: u32 = 192; // defmsg(CA_K_WARNING, 24)
81const ECA_PUTCBINPROG: u32 = 366; // defmsg(CA_K_ERROR, 45)
82
83impl CaError {
84    pub fn to_eca_status(&self) -> u32 {
85        match self {
86            CaError::Timeout => ECA_TIMEOUT,
87            CaError::ReadOnlyField(_) => ECA_NOWTACCESS,
88            CaError::PutDisabled(_) => ECA_PUTFAIL,
89            CaError::TypeMismatch(_) => ECA_BADTYPE,
90            CaError::UnsupportedType(_) => ECA_BADTYPE,
91            CaError::InvalidValue(_) => ECA_BADTYPE,
92            CaError::FieldNotFound(_) => ECA_PUTFAIL,
93            // Disconnection / shutdown are surfaced as ECA_DISCONN so a
94            // downstream client (e.g. caput on a CA gateway whose
95            // upstream just dropped) sees the actionable
96            // "CA channel disconnected" message rather than the
97            // catch-all "Put fail".
98            CaError::Disconnected | CaError::Shutdown => ECA_DISCONN,
99            // I/O errors usually mean the upstream connection is
100            // wedged; mapping to ECA_DISCONN matches operator
101            // expectations from C ca-gateway.
102            CaError::Io(_) => ECA_DISCONN,
103            CaError::WriteFailed(code) => *code,
104            CaError::PutCallbackInProgress(_) => ECA_PUTCBINPROG,
105            CaError::ServerError(code) => *code,
106            _ => ECA_PUTFAIL,
107        }
108    }
109}
110
111pub type CaResult<T> = Result<T, CaError>;