use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Error, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AuthError {
#[error("missing secret for auth resolution")]
MissingSecret,
#[error("unsupported combination: backend={backend} auth={auth}")]
UnsupportedCombination { backend: String, auth: String },
#[error("missing required metadata: {0}")]
MissingRequiredMetadata(String),
#[error("workspace mismatch")]
WorkspaceMismatch,
#[error("stale credential material")]
StaleCredential,
#[error("credential refresh required")]
RefreshRequired,
#[error("auth lease absent")]
LeaseAbsent,
#[error("user reauth required")]
UserReauthRequired,
#[error("credential expired")]
Expired,
#[error("refresh failed: {0}")]
RefreshFailed(String),
#[error("interactive login required")]
InteractiveLoginRequired,
#[error("host-owned auth unavailable on this surface")]
HostOwnedUnavailable,
#[error("auth I/O failure: {0}")]
Io(String),
#[error("auth error: {0}")]
Other(String),
}
impl AuthError {
pub fn kind(&self) -> AuthErrorKind {
match self {
Self::MissingSecret => AuthErrorKind::MissingSecret,
Self::UnsupportedCombination { .. } => AuthErrorKind::UnsupportedCombination,
Self::MissingRequiredMetadata(_) => AuthErrorKind::MissingRequiredMetadata,
Self::WorkspaceMismatch => AuthErrorKind::WorkspaceMismatch,
Self::StaleCredential => AuthErrorKind::StaleCredential,
Self::RefreshRequired => AuthErrorKind::RefreshRequired,
Self::LeaseAbsent => AuthErrorKind::LeaseAbsent,
Self::UserReauthRequired => AuthErrorKind::UserReauthRequired,
Self::Expired => AuthErrorKind::Expired,
Self::RefreshFailed(_) => AuthErrorKind::RefreshFailed,
Self::InteractiveLoginRequired => AuthErrorKind::InteractiveLoginRequired,
Self::HostOwnedUnavailable => AuthErrorKind::HostOwnedUnavailable,
Self::Io(_) => AuthErrorKind::Io,
Self::Other(_) => AuthErrorKind::Other,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum AuthErrorKind {
MissingSecret,
UnsupportedCombination,
MissingRequiredMetadata,
WorkspaceMismatch,
StaleCredential,
RefreshRequired,
LeaseAbsent,
UserReauthRequired,
Expired,
RefreshFailed,
InteractiveLoginRequired,
HostOwnedUnavailable,
Io,
Other,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn kind_maps_each_variant() {
assert_eq!(
AuthError::MissingSecret.kind(),
AuthErrorKind::MissingSecret
);
assert_eq!(
AuthError::WorkspaceMismatch.kind(),
AuthErrorKind::WorkspaceMismatch
);
assert_eq!(
AuthError::StaleCredential.kind(),
AuthErrorKind::StaleCredential
);
assert_eq!(
AuthError::RefreshRequired.kind(),
AuthErrorKind::RefreshRequired
);
assert_eq!(AuthError::LeaseAbsent.kind(), AuthErrorKind::LeaseAbsent);
assert_eq!(
AuthError::UserReauthRequired.kind(),
AuthErrorKind::UserReauthRequired
);
assert_eq!(AuthError::Expired.kind(), AuthErrorKind::Expired);
assert_eq!(
AuthError::RefreshFailed("x".into()).kind(),
AuthErrorKind::RefreshFailed,
);
assert_eq!(
AuthError::InteractiveLoginRequired.kind(),
AuthErrorKind::InteractiveLoginRequired,
);
}
#[test]
fn display_is_stable_nonempty() {
for err in [
AuthError::MissingSecret,
AuthError::UnsupportedCombination {
backend: "b".into(),
auth: "a".into(),
},
AuthError::MissingRequiredMetadata("workspace_id".into()),
AuthError::WorkspaceMismatch,
AuthError::StaleCredential,
AuthError::RefreshRequired,
AuthError::LeaseAbsent,
AuthError::UserReauthRequired,
AuthError::Expired,
AuthError::RefreshFailed("timeout".into()),
AuthError::InteractiveLoginRequired,
AuthError::HostOwnedUnavailable,
AuthError::Io("file missing".into()),
AuthError::Other("x".into()),
] {
assert!(!err.to_string().is_empty(), "{err:?}");
}
}
#[test]
fn error_kind_serde_roundtrip() {
let k = AuthErrorKind::MissingSecret;
let s = serde_json::to_string(&k).unwrap();
assert_eq!(s, "\"missing_secret\"");
let back: AuthErrorKind = serde_json::from_str(&s).unwrap();
assert_eq!(back, k);
}
}