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 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}