Skip to main content

instrument_core/
error.rs

1use crate::kind::InstrumentKind;
2
3pub type Result<T> = std::result::Result<T, Error>;
4
5/// Core error type — VISA-free; backend errors are boxed.
6#[derive(Debug, thiserror::Error)]
7#[non_exhaustive]
8pub enum Error {
9    #[error("transport: {0}")]
10    Transport(#[from] TransportError),
11    #[error("backend: {0}")]
12    Backend(Box<dyn std::error::Error + Send + Sync>),
13    #[error("SCPI command '{command}' failed: {message}")]
14    ScpiCommand { command: String, message: String },
15    #[error("operation timed out")]
16    Timeout,
17    #[error("unsupported: {0}")]
18    Unsupported(&'static str),
19    #[error("device not found: {address}")]
20    DeviceNotFound { address: String },
21    #[error("kind {kind:?} not supported at {address}; supported: {supported:?}")]
22    UnsupportedKind {
23        address: String,
24        kind: InstrumentKind,
25        supported: Vec<InstrumentKind>,
26    },
27    #[error("parse error: {0}")]
28    Parse(String),
29    #[error("session limit reached for {address}")]
30    SessionLimit { address: String },
31    #[error("invalid address: {0}")]
32    InvalidAddress(String),
33    #[error("mock script exhausted: expected write")]
34    MockExhausted,
35    #[error("mock script mismatch: expected {expected:?}, got {actual:?}")]
36    MockMismatch { expected: String, actual: String },
37    #[error("communication failed at {address} after {attempts} attempt(s): {source}")]
38    Comm {
39        address: String,
40        command: Option<String>,
41        attempts: u32,
42        source: Box<dyn std::error::Error + Send + Sync>,
43    },
44}
45
46impl Error {
47    /// Wraps a backend-specific error (e.g. visa_rs::Error) into the core error type.
48    pub fn backend(err: impl std::error::Error + Send + Sync + 'static) -> Self {
49        Self::Backend(Box::new(err))
50    }
51
52    /// Attaches device address and command context to a communication error.
53    pub fn with_comm_context(
54        self,
55        address: impl Into<String>,
56        command: Option<&str>,
57        attempts: u32,
58    ) -> Self {
59        let address = address.into();
60        match self {
61            Self::Comm { .. } => self,
62            other => Self::Comm {
63                address,
64                command: command.map(str::to_string),
65                attempts,
66                source: Box::new(other),
67            },
68        }
69    }
70}
71
72#[derive(Debug, thiserror::Error)]
73#[non_exhaustive]
74pub enum TransportError {
75    #[error("I/O error: {0}")]
76    Io(String),
77    #[error("connection closed")]
78    Closed,
79}