use rootcause::Report;
use thiserror::Error;
use uptrakit_shared_macros::impl_report_conversion;
pub type Result<T> = std::result::Result<T, Report<OAuthError>>;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum OAuthError {
#[error("invalid_request: {0}")]
InvalidRequest(String),
#[error("invalid_client")]
InvalidClient,
#[error("invalid_grant: {0}")]
InvalidGrant(&'static str),
#[error("unauthorized_client")]
UnauthorizedClient,
#[error("unsupported_grant_type")]
UnsupportedGrantType,
#[error("invalid_scope")]
InvalidScope,
#[error("invalid_target")]
InvalidTarget,
#[error("access_denied")]
AccessDenied,
#[error("server_error")]
ServerError,
#[error("temporarily_unavailable")]
TemporarilyUnavailable,
#[error("insufficient_scope")]
InsufficientScope,
#[error("database error")]
Database(sea_orm::DbErr),
}
impl OAuthError {
#[must_use]
pub fn error_code(&self) -> &'static str {
match self {
OAuthError::InvalidRequest(_) => "invalid_request",
OAuthError::InvalidClient => "invalid_client",
OAuthError::InvalidGrant(_) => "invalid_grant",
OAuthError::UnauthorizedClient => "unauthorized_client",
OAuthError::UnsupportedGrantType => "unsupported_grant_type",
OAuthError::InvalidScope => "invalid_scope",
OAuthError::InvalidTarget => "invalid_target",
OAuthError::AccessDenied => "access_denied",
OAuthError::ServerError => "server_error",
OAuthError::TemporarilyUnavailable => "temporarily_unavailable",
OAuthError::InsufficientScope => "insufficient_scope",
OAuthError::Database(_) => "server_error",
}
}
#[must_use]
pub fn http_status(&self) -> u16 {
match self {
OAuthError::InvalidRequest(_)
| OAuthError::InvalidGrant(_)
| OAuthError::UnsupportedGrantType
| OAuthError::InvalidScope
| OAuthError::InvalidTarget => 400,
OAuthError::InvalidClient | OAuthError::UnauthorizedClient => 401,
OAuthError::AccessDenied | OAuthError::InsufficientScope => 403,
OAuthError::ServerError | OAuthError::Database(_) => 500,
OAuthError::TemporarilyUnavailable => 503,
}
}
}
impl_report_conversion!(sea_orm::DbErr => OAuthError::Database);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc6749_codes_match_spec() {
assert_eq!(
OAuthError::InvalidGrant("test").error_code(),
"invalid_grant"
);
assert_eq!(OAuthError::InvalidTarget.error_code(), "invalid_target");
assert_eq!(OAuthError::InvalidClient.error_code(), "invalid_client");
assert_eq!(
OAuthError::InsufficientScope.error_code(),
"insufficient_scope"
);
}
#[test]
fn http_status_mapping_correct() {
assert_eq!(OAuthError::InvalidGrant("x").http_status(), 400);
assert_eq!(OAuthError::InvalidClient.http_status(), 401);
assert_eq!(OAuthError::AccessDenied.http_status(), 403);
assert_eq!(OAuthError::InsufficientScope.http_status(), 403);
assert_eq!(OAuthError::ServerError.http_status(), 500);
assert_eq!(OAuthError::TemporarilyUnavailable.http_status(), 503);
}
}