Skip to main content

entelix_auth_claude_code/
error.rs

1//! Error variants for the Claude Code OAuth provider.
2
3use thiserror::Error;
4
5/// Failure modes specific to Claude Code OAuth credential resolution.
6///
7/// Each variant maps onto a [`entelix_core::auth::AuthError`] when
8/// surfaced through the [`entelix_core::auth::CredentialProvider`]
9/// trait.
10#[derive(Debug, Error)]
11#[non_exhaustive]
12pub enum ClaudeCodeAuthError {
13    /// The credential file is absent — typically the operator hasn't
14    /// run `claude login` yet.
15    #[error("Claude Code credentials not found at `{path}` — run `claude login` first")]
16    CredentialsMissing {
17        /// Resolved storage path that was checked.
18        path: String,
19    },
20    /// The credential file exists but contains no OAuth section.
21    #[error("Claude Code credential file `{path}` has no OAuth section")]
22    OAuthSectionMissing {
23        /// Resolved storage path of the credential file.
24        path: String,
25    },
26    /// The OAuth credential payload had no `refresh_token` — the
27    /// access token is expired and cannot be renewed.
28    #[error("Claude Code OAuth refresh token absent — re-authenticate via `claude login`")]
29    RefreshTokenMissing,
30    /// The token-refresh HTTP call failed.
31    #[error("Claude Code OAuth refresh failed: {message}")]
32    RefreshHttp {
33        /// Human-readable cause forwarded from `reqwest` or the OAuth
34        /// server's error body.
35        message: String,
36    },
37    /// The credential file failed to parse as JSON.
38    #[error("Claude Code credential file `{path}` is not valid JSON: {source}")]
39    InvalidStorage {
40        /// Resolved storage path of the credential file.
41        path: String,
42        /// Underlying serde error.
43        #[source]
44        source: serde_json::Error,
45    },
46    /// Filesystem error reading or writing the credential file.
47    #[error("Claude Code credential file `{path}` IO failed: {source}")]
48    Io {
49        /// Resolved storage path of the credential file.
50        path: String,
51        /// Underlying IO error.
52        #[source]
53        source: std::io::Error,
54    },
55    /// `HOME` (or platform equivalent) is unset, so the default
56    /// `~/.claude/.credentials.json` location cannot be resolved.
57    #[error("home directory not resolvable — supply the credentials path explicitly")]
58    HomeUnresolved,
59}
60
61/// Convenience result alias.
62pub type ClaudeCodeAuthResult<T> = Result<T, ClaudeCodeAuthError>;
63
64impl From<ClaudeCodeAuthError> for entelix_core::Error {
65    fn from(err: ClaudeCodeAuthError) -> Self {
66        use entelix_core::auth::AuthError;
67        let message = err.to_string();
68        match err {
69            ClaudeCodeAuthError::CredentialsMissing { .. }
70            | ClaudeCodeAuthError::OAuthSectionMissing { .. }
71            | ClaudeCodeAuthError::HomeUnresolved => Self::Auth(AuthError::missing_from(message)),
72            ClaudeCodeAuthError::RefreshTokenMissing => {
73                Self::Auth(AuthError::expired_with(message))
74            }
75            ClaudeCodeAuthError::RefreshHttp { .. } => {
76                Self::Auth(AuthError::source_unreachable(message))
77            }
78            ClaudeCodeAuthError::InvalidStorage { .. } | ClaudeCodeAuthError::Io { .. } => {
79                Self::Auth(AuthError::refused(message))
80            }
81        }
82    }
83}