Skip to main content

incus_client/
error.rs

1/// Errors returned by [`crate::Client`] and every resource method built on
2/// top of it.
3///
4/// `#[non_exhaustive]` because this crate expects to grow new failure modes
5/// as more of the Incus API surface is covered - matching exhaustively on
6/// this enum today would make every future variant addition a breaking
7/// change for downstream crates.
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum Error {
11    #[error("transport I/O error: {0}")]
12    Transport(#[from] std::io::Error),
13
14    #[error("Incus API error (status {status_code}): {message}")]
15    Api { status_code: u16, message: String },
16
17    #[error("failed to (de)serialize a request or response body: {0}")]
18    Serialization(#[from] serde_json::Error),
19
20    #[error("response did not match any known Incus envelope shape: {0}")]
21    InvalidResponse(String),
22
23    #[error("response body exceeded the {limit}-byte cap")]
24    ResponseTooLarge { limit: usize },
25
26    #[error("operation is not cancellable (may_cancel is false)")]
27    NotCancellable,
28
29    #[error("operation {id} failed (status {status_code}): {}", err.as_deref().unwrap_or("no error message"))]
30    OperationFailed {
31        id: uuid::Uuid,
32        status_code: u16,
33        err: Option<String>,
34    },
35
36    #[error("precondition failed updating {resource} (stale ETag - re-fetch and retry)")]
37    PreconditionFailed { resource: String },
38
39    #[error("{resource} not found")]
40    NotFound { resource: String },
41
42    #[error("invalid request: {0}")]
43    InvalidRequest(String),
44
45    /// A WebSocket-level failure from the `events` feature's
46    /// `/1.0/events` subscription that is *not* a plain socket I/O error
47    /// (those still map to `Transport`) - a protocol violation, oversized
48    /// frame, or similar. Kept separate from `Transport` so a caller can
49    /// tell "the connection itself broke" apart from "the daemon sent
50    /// something the WebSocket layer rejected" without string-matching.
51    #[cfg(feature = "events")]
52    #[error("WebSocket protocol error on /1.0/events: {0}")]
53    WebSocketProtocol(String),
54
55    /// `request_fully_sent` tells a caller building retry logic whether the
56    /// request had already been fully written to the daemon when the
57    /// timeout fired: if `true`, a mutating call (create/update/delete) may
58    /// have already been received and acted on server-side even though the
59    /// caller only sees this timeout - retrying could duplicate the
60    /// operation. If `false`, nothing was sent and a retry is safe. Incus
61    /// operations are not inherently idempotent, so this distinction
62    /// matters for anything more than a manual "try again and see."
63    #[error("request timed out after {after:?} (request fully sent: {request_fully_sent})")]
64    Timeout {
65        after: std::time::Duration,
66        request_fully_sent: bool,
67    },
68}
69
70pub type Result<T> = std::result::Result<T, Error>;
71
72#[cfg(test)]
73#[path = "error_tests.rs"]
74mod tests;