lager/error.rs
1//! Crate-wide error type.
2
3use std::fmt;
4
5/// Convenience alias used by every fallible API in this crate.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// All the ways a Lager box interaction can fail.
9#[derive(Debug)]
10#[non_exhaustive]
11pub enum Error {
12 /// The box could not be reached at all (DNS, TCP connect, transport
13 /// failure). Check network/Tailscale and that the box is online.
14 Connection(String),
15 /// The HTTP request timed out client-side. For long-running actions
16 /// (energy integration windows, `wait_for_level`) the crate widens the
17 /// timeout automatically, so hitting this usually means the box stalled.
18 Timeout(String),
19 /// The box accepted the request but reported failure. Carries the HTTP
20 /// status and the box's `error` message. Note the box can report failure
21 /// with HTTP 200 (e.g. a cross-role instrument conflict); this variant
22 /// covers that too.
23 Box {
24 /// HTTP status code of the response (200 is possible: the box
25 /// reports some conflicts as `success: false` with HTTP 200).
26 status: u16,
27 /// Human-readable error message from the box.
28 message: String,
29 },
30 /// Box-side `dfu-util` ran and exited non-zero (HTTP 502 from
31 /// `POST /usb/dfu`). Carries both captured streams in full, and
32 /// `Display` quotes both: dfu-util reports a device failure on stdout
33 /// for a raw download and on stderr for DfuSe, and its DFU suffix
34 /// warnings on stderr.
35 DfuFailed {
36 /// HTTP status code of the response (502).
37 status: u16,
38 /// dfu-util's exit code.
39 exit_code: Option<i64>,
40 /// dfu-util's captured stdout.
41 stdout: String,
42 /// dfu-util's captured stderr.
43 stderr: String,
44 },
45 /// A debug `flash` ran but programmed nothing (box >= 0.51.0). The box
46 /// answers HTTP 200 with `programmed: false` for this, so before this
47 /// variant the crate reported success.
48 FlashFailed {
49 /// The box's verdict: the line showing nothing was programmed.
50 error: String,
51 /// The backend's output.
52 output: Vec<String>,
53 },
54 /// The debug probe stayed busy with another operation (a flash, erase,
55 /// reset, ...) for longer than the box waits (HTTP 503 from the debug
56 /// service, box >= 0.51.0). Retry once the other operation is done.
57 ProbeBusy {
58 /// The box's message, naming the operation holding the probe.
59 message: String,
60 },
61 /// The net's safety settings refused a destructive debug operation
62 /// (flash or erase; HTTP 403 from the debug service). See
63 /// [`crate::SafetyLimits::allow_destructive`].
64 SafetyRefused {
65 /// The box's message.
66 message: String,
67 },
68 /// HTTP 501: this box image does not serve the endpoint. The box needs a
69 /// software update.
70 UnsupportedByBox {
71 /// Human-readable error message from the box.
72 message: String,
73 },
74 /// The box's response could not be parsed into the expected shape.
75 Decode(String),
76 /// The net type exists in the Lager Python API but its box endpoint is
77 /// not yet available on the `:9000` HTTP API, so this crate ships it as a
78 /// documented stub. No net type returns this since the scope stub became
79 /// a real net ([`crate::nets::scope`]) in 0.8.0; the variant stays so matches
80 /// on it keep compiling.
81 NotSupportedByBox {
82 /// Which net/feature was invoked (e.g. `"debug"`).
83 feature: &'static str,
84 /// What the box side would need to expose for this to work.
85 details: &'static str,
86 },
87 /// The box sits behind an authenticating gateway and the request was
88 /// denied (HTTP 401 with the `X-Gateway-Auth-Url` discovery header),
89 /// with no usable credential available. Sign in with the Lager CLI
90 /// (`lager login <auth_url>`) — this crate reuses the CLI's session —
91 /// or supply a token via `LagerBoxBuilder::bearer_token` /
92 /// `LAGER_GATEWAY_TOKEN`.
93 AuthRequired {
94 /// Hostname of the gated box.
95 box_host: String,
96 /// The auth server URL announced by the gateway.
97 auth_url: String,
98 /// What happened (no credential vs. rejected credential).
99 message: String,
100 },
101 /// Client-side configuration problem (bad host string, missing env var).
102 Config(String),
103 /// A streaming session (UART) reported an error, e.g. the net is in use
104 /// by another session or the device disappeared.
105 Stream(String),
106}
107
108impl fmt::Display for Error {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 Error::Connection(msg) => write!(
112 f,
113 "cannot reach the Lager box: {msg}. Check network/Tailscale and that the box is online and updated"
114 ),
115 Error::Timeout(msg) => write!(f, "request to the Lager box timed out: {msg}"),
116 Error::Box { status, message } => {
117 write!(f, "box error (HTTP {status}): {message}")
118 }
119 Error::DfuFailed {
120 status,
121 exit_code,
122 stdout,
123 stderr,
124 } => {
125 write!(f, "box error (HTTP {status}): dfu-util ")?;
126 match exit_code {
127 Some(code) => write!(f, "exited with code {code}")?,
128 None => write!(f, "failed")?,
129 }
130 let (stderr, stdout) = (stderr.trim(), stdout.trim());
131 if stderr.is_empty() && stdout.is_empty() {
132 return write!(f, ": no output");
133 }
134 if !stderr.is_empty() {
135 write!(f, "\nstderr:\n{stderr}")?;
136 }
137 if !stdout.is_empty() {
138 write!(f, "\nstdout:\n{stdout}")?;
139 }
140 Ok(())
141 }
142 Error::FlashFailed { error, output } => {
143 write!(f, "flash failed: {error}")?;
144 if !output.is_empty() {
145 write!(f, "\noutput:\n{}", output.join("\n"))?;
146 }
147 Ok(())
148 }
149 Error::ProbeBusy { message } => write!(f, "debug probe busy: {message}"),
150 Error::SafetyRefused { message } => {
151 write!(f, "refused by the net's safety settings: {message}")
152 }
153 Error::UnsupportedByBox { message } => write!(
154 f,
155 "{message}. This box image does not support this endpoint; update the box"
156 ),
157 Error::Decode(msg) => write!(f, "could not decode box response: {msg}"),
158 Error::NotSupportedByBox { feature, details } => write!(
159 f,
160 "'{feature}' is not yet available over the box HTTP API: {details}"
161 ),
162 Error::AuthRequired { auth_url, message, .. } => write!(
163 f,
164 "{message}. Sign in with `lager login {auth_url}` (this crate reuses the \
165 CLI's session), or set LAGER_GATEWAY_TOKEN / use \
166 LagerBoxBuilder::bearer_token"
167 ),
168 Error::Config(msg) => write!(f, "configuration error: {msg}"),
169 Error::Stream(msg) => write!(f, "streaming session error: {msg}"),
170 }
171 }
172}
173
174impl std::error::Error for Error {}
175
176impl From<serde_json::Error> for Error {
177 fn from(e: serde_json::Error) -> Self {
178 Error::Decode(e.to_string())
179 }
180}