Skip to main content

sts_cat/
error.rs

1#[derive(Debug, thiserror::Error)]
2pub enum Error {
3    #[error("bad request: {0}")]
4    BadRequest(String),
5
6    #[error("unauthenticated: {0}")]
7    Unauthenticated(String),
8
9    #[error("permission denied: {0}")]
10    PermissionDenied(String),
11
12    #[error("not found: {0}")]
13    NotFound(String),
14
15    #[error("rate limited")]
16    RateLimited,
17
18    #[error("GitHub API error")]
19    GitHubApi(#[source] reqwest::Error),
20
21    #[error("OIDC discovery error")]
22    OidcDiscovery(#[source] reqwest::Error),
23
24    #[error("OIDC HTTP error: {0}")]
25    OidcHttpError(u16),
26
27    #[error("JWT verification failed")]
28    JwtVerification(#[from] jsonwebtoken::errors::Error),
29
30    #[error("trust policy parse error")]
31    PolicyParse(#[from] toml::de::Error),
32
33    #[error("regex compilation error")]
34    RegexCompile(#[from] regex::Error),
35
36    #[error("internal error: {0}")]
37    Internal(Box<dyn std::error::Error + Send + Sync>),
38}
39
40#[derive(serde::Serialize)]
41struct ErrorBody<'a> {
42    error: &'a str,
43}
44
45impl axum::response::IntoResponse for Error {
46    fn into_response(self) -> axum::response::Response {
47        let (status, message) = match &self {
48            Error::BadRequest(_) => (axum::http::StatusCode::BAD_REQUEST, "bad request"),
49            Error::Unauthenticated(_) => (
50                axum::http::StatusCode::UNAUTHORIZED,
51                "unable to verify bearer token",
52            ),
53            Error::PermissionDenied(_) => (axum::http::StatusCode::FORBIDDEN, "permission denied"),
54            Error::NotFound(_) => (axum::http::StatusCode::NOT_FOUND, "not found"),
55            Error::RateLimited => (axum::http::StatusCode::TOO_MANY_REQUESTS, "rate limited"),
56            Error::GitHubApi(e) => {
57                tracing::error!(error = ?e, "GitHub API error");
58                (
59                    axum::http::StatusCode::INTERNAL_SERVER_ERROR,
60                    "internal error",
61                )
62            }
63            Error::OidcDiscovery(e) => {
64                tracing::error!(error = ?e, "OIDC discovery error");
65                (
66                    axum::http::StatusCode::INTERNAL_SERVER_ERROR,
67                    "internal error",
68                )
69            }
70            Error::OidcHttpError(code) => {
71                tracing::debug!(status = code, "OIDC HTTP error");
72                (
73                    axum::http::StatusCode::UNAUTHORIZED,
74                    "unable to verify bearer token",
75                )
76            }
77            Error::JwtVerification(_) => (
78                axum::http::StatusCode::UNAUTHORIZED,
79                "unable to verify bearer token",
80            ),
81            Error::PolicyParse(e) => {
82                tracing::debug!(error = %e, "trust policy parse error");
83                (axum::http::StatusCode::NOT_FOUND, "not found")
84            }
85            Error::RegexCompile(e) => {
86                tracing::debug!(error = %e, "regex compilation error in trust policy");
87                (axum::http::StatusCode::NOT_FOUND, "not found")
88            }
89            Error::Internal(e) => {
90                tracing::error!(error = ?e, "internal error");
91                (
92                    axum::http::StatusCode::INTERNAL_SERVER_ERROR,
93                    "internal error",
94                )
95            }
96        };
97
98        // Log 4xx at debug, 5xx already logged above
99        match &self {
100            Error::BadRequest(msg) => tracing::debug!(error = %msg, "bad request"),
101            Error::Unauthenticated(msg) => tracing::debug!(error = %msg, "unauthenticated"),
102            Error::PermissionDenied(msg) => tracing::debug!(error = %msg, "permission denied"),
103            Error::NotFound(msg) => tracing::debug!(error = %msg, "not found"),
104            Error::JwtVerification(e) => tracing::debug!(error = %e, "JWT verification failed"),
105            _ => {}
106        }
107
108        let body = ErrorBody { error: message };
109        (status, axum::Json(body)).into_response()
110    }
111}