Skip to main content

pidge_client/
error.rs

1//! Error type for `pidge-client`.
2
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum ClientError {
7    #[error(
8        "pidge has not been provisioned yet. The maintainer needs to run scripts/register-pidge-app.sh and update APP_CLIENT_ID."
9    )]
10    NotProvisioned,
11
12    #[error("OS keychain unavailable: {0}")]
13    Keychain(#[from] keyring::Error),
14
15    #[error("could not determine the user config directory")]
16    NoConfigDir,
17
18    #[error("io: {0}")]
19    Io(#[from] std::io::Error),
20
21    #[error("device code flow: authorization pending exceeded poll deadline")]
22    DeviceCodeTimeout,
23
24    #[error("device code flow: user denied consent")]
25    DeviceCodeAccessDenied,
26
27    #[error("device code flow: {kind}{}", description.as_ref().map(|d| format!(": {d}")).unwrap_or_default())]
28    DeviceCodeOther {
29        kind: String,
30        description: Option<String>,
31    },
32
33    #[error("session expired for {email}. Run `pidge account add` to re-add this account.")]
34    SessionExpired { email: String },
35
36    /// A hosted pidge MCP session (`pidge mcp connect` et al.) turned out to
37    /// be unrefreshable. Distinct from [`Self::SessionExpired`], which hints
38    /// at `pidge account add` (the fix for a Microsoft account's own
39    /// session, not a hosted MCP one), so this carries its own message and
40    /// its own `exitcode::classify` arm.
41    #[error(
42        "session expired for the hosted pidge server. Run `pidge mcp connect {server}` to sign in again."
43    )]
44    McpSessionExpired { server: String },
45
46    #[error("http: {0}")]
47    Http(#[from] reqwest::Error),
48
49    #[error("json: {0}")]
50    Json(#[from] serde_json::Error),
51
52    #[error("Microsoft Graph: {status} {message}")]
53    Graph { status: u16, message: String },
54
55    #[error(
56        "Microsoft Graph is throttling requests{}. Try again later.",
57        retry_after.map(|s| format!(" (retry after {s}s)")).unwrap_or_default()
58    )]
59    Throttled { retry_after: Option<u64> },
60
61    #[error("the delta cursor has expired; re-bootstrap without --cursor")]
62    DeltaExpired,
63
64    #[error("core: {0}")]
65    Core(#[from] pidge_core::CoreError),
66
67    /// A one-click unsubscribe POST was refused before sending (not https,
68    /// an internal or literal address) or failed (a non-2xx answer, a
69    /// redirect, a network error). Deliberately carries no detail.
70    #[error("unsubscribe request rejected")]
71    UnsubscribeRejected,
72
73    #[error("token response missing access_token")]
74    MissingAccessToken,
75}