Skip to main content

systemprompt_api/routes/oauth/error/
code.rs

1//! RFC 6749 §5.2 error codes plus the `WebAuthn` / RFC 7591 extensions this
2//! server emits, and their default HTTP status mapping.
3//!
4//! `Display` (via [`OAuthErrorCode::as_str`]) yields the wire string. The
5//! default status follows §5.2: token-endpoint errors return 400 except
6//! `invalid_client`, which RFC 6749 permits to return 401 to advertise
7//! authentication schemes — and so we do. `access_denied`, `invalid_token`,
8//! and `authentication_failed` retain 401 because they signal that the
9//! *caller* (not the request) was rejected (RFC 6750 §3.1).
10
11use axum::http::StatusCode;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum OAuthErrorCode {
15    InvalidRequest,
16    InvalidClient,
17    InvalidGrant,
18    UnauthorizedClient,
19    UnsupportedGrantType,
20    InvalidScope,
21    InvalidToken,
22    AccessDenied,
23    ServerError,
24    TemporarilyUnavailable,
25    InvalidClientMetadata,
26    AuthenticationFailed,
27    RegistrationFailed,
28    UsernameUnavailable,
29    EmailExists,
30    ExpiredChallenge,
31    InvalidCredential,
32    LinkFailed,
33    InvalidTarget,
34    NotFound,
35}
36
37impl OAuthErrorCode {
38    #[must_use]
39    pub const fn as_str(self) -> &'static str {
40        match self {
41            Self::InvalidRequest => "invalid_request",
42            Self::InvalidClient => "invalid_client",
43            Self::InvalidGrant => "invalid_grant",
44            Self::UnauthorizedClient => "unauthorized_client",
45            Self::UnsupportedGrantType => "unsupported_grant_type",
46            Self::InvalidScope => "invalid_scope",
47            Self::InvalidToken => "invalid_token",
48            Self::AccessDenied => "access_denied",
49            Self::ServerError => "server_error",
50            Self::TemporarilyUnavailable => "temporarily_unavailable",
51            Self::InvalidClientMetadata => "invalid_client_metadata",
52            Self::AuthenticationFailed => "authentication_failed",
53            Self::RegistrationFailed => "registration_failed",
54            Self::UsernameUnavailable => "username_unavailable",
55            Self::EmailExists => "email_exists",
56            Self::ExpiredChallenge => "expired_challenge",
57            Self::InvalidCredential => "invalid_credential",
58            Self::LinkFailed => "link_failed",
59            Self::InvalidTarget => "invalid_target",
60            Self::NotFound => "not_found",
61        }
62    }
63
64    #[must_use]
65    pub const fn default_status(self) -> StatusCode {
66        match self {
67            Self::InvalidRequest
68            | Self::InvalidGrant
69            | Self::UnauthorizedClient
70            | Self::UnsupportedGrantType
71            | Self::InvalidScope
72            | Self::InvalidClientMetadata
73            | Self::ExpiredChallenge
74            | Self::InvalidCredential
75            | Self::LinkFailed
76            | Self::InvalidTarget
77            | Self::RegistrationFailed => StatusCode::BAD_REQUEST,
78            Self::InvalidClient
79            | Self::AccessDenied
80            | Self::AuthenticationFailed
81            | Self::InvalidToken => StatusCode::UNAUTHORIZED,
82            Self::UsernameUnavailable | Self::EmailExists => StatusCode::CONFLICT,
83            Self::NotFound => StatusCode::NOT_FOUND,
84            Self::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
85            Self::TemporarilyUnavailable => StatusCode::SERVICE_UNAVAILABLE,
86        }
87    }
88}