Skip to main content

pb_mapper_client/sdk/
error.rs

1//! The SDK's error type.
2//!
3//! One flat enum, deliberately: a caller has to be able to match on what went
4//! wrong without importing the error type of every layer below.
5
6use std::time::Duration;
7
8use snafu::Snafu;
9
10use pb_mapper_protocol::command::PbErrorResponse;
11
12/// Errors returned by the client SDK.
13#[derive(Debug, Snafu)]
14#[snafu(visibility(pub(crate)))]
15pub enum Error {
16    #[snafu(display("{message}"))]
17    InvalidConfig { message: String },
18    #[snafu(display("not an administrator credential"))]
19    NotAdministrator,
20    #[snafu(display("invalid address `{addr}`: {source}"))]
21    Address {
22        addr: String,
23        source: pb_mapper_core::error::Error,
24    },
25    #[snafu(display("connect to `{addr}` failed: {source}"))]
26    Connect {
27        addr: String,
28        source: std::io::Error,
29    },
30    #[snafu(display("{code}: {message}"))]
31    Remote {
32        code: String,
33        message: String,
34        retryable: bool,
35    },
36    #[snafu(display("{message}"))]
37    Protocol { message: String },
38    #[snafu(display("timed out waiting for the tunnel to become ready after {timeout:?}"))]
39    ReadyTimeout { timeout: Duration },
40    #[snafu(display("administrator request timed out after {timeout:?}"))]
41    TimedOut { timeout: Duration },
42    #[snafu(display("tunnel failed: {reason}"))]
43    TunnelFailed { reason: String },
44    #[snafu(display("tunnel stopped before becoming ready"))]
45    Stopped,
46    #[snafu(display("{source}"))]
47    Status { source: crate::client::error::Error },
48    #[snafu(display("{source}"))]
49    Io { source: std::io::Error },
50    #[snafu(display("{message}"))]
51    AuthFile { message: String },
52    /// A root rotation whose outcome the caller could not learn, carrying the
53    /// candidate the SDK generated on their behalf.
54    ///
55    /// The rotation is not idempotent: if the relay committed it and the
56    /// response was lost, the candidate is the relay's active administrator key
57    /// and the only one that still authenticates. It is repeated in the display
58    /// text deliberately — the Node binding flattens errors to a string, and a
59    /// key that only lives in a structured field would be lost there, locking
60    /// the operator out of their own relay.
61    #[snafu(display(
62        "root rotation did not report success, and the relay may already have installed the \
63         generated administrator key `{candidate}`; treat it as the active key: {message}"
64    ))]
65    RootRotationUncertain { candidate: String, message: String },
66}
67
68impl Error {
69    pub(crate) fn invalid_config(message: impl Into<String>) -> Self {
70        Self::InvalidConfig {
71            message: message.into(),
72        }
73    }
74
75    pub(crate) fn protocol(message: impl Into<String>) -> Self {
76        Self::Protocol {
77            message: message.into(),
78        }
79    }
80
81    pub(crate) fn from_remote(error: PbErrorResponse) -> Self {
82        Self::Remote {
83            code: error.code,
84            message: error.message,
85            retryable: error.retryable,
86        }
87    }
88}
89
90pub type Result<T, E = Error> = std::result::Result<T, E>;