pacenotes_server/
error.rs1pub struct AppError {
3 pub status: axum::http::StatusCode,
4 pub code: &'static str,
5 pub message: String,
6 pub retry_after: Option<u64>,
8}
9
10impl AppError {
11 pub fn unauthorized() -> Self {
12 Self {
13 status: axum::http::StatusCode::UNAUTHORIZED,
14 code: "unauthorized",
15 message: "A valid access key is required for this package.".to_string(),
16 retry_after: None,
17 }
18 }
19
20 pub fn invalid_key() -> Self {
22 Self {
23 status: axum::http::StatusCode::UNAUTHORIZED,
24 code: "unauthorized",
25 message: "This access key is not valid, or has expired.".to_string(),
26 retry_after: None,
27 }
28 }
29
30 pub fn not_found() -> Self {
31 Self {
32 status: axum::http::StatusCode::NOT_FOUND,
33 code: "not_found",
34 message: "No such package.".to_string(),
35 retry_after: None,
36 }
37 }
38
39 pub fn unavailable() -> Self {
42 Self {
43 status: axum::http::StatusCode::SERVICE_UNAVAILABLE,
44 code: "unavailable",
45 message: "This package is being updated on the server. Try again shortly.".to_string(),
46 retry_after: None,
47 }
48 }
49
50 pub fn rate_limited(retry_after: u64) -> Self {
52 Self {
53 status: axum::http::StatusCode::TOO_MANY_REQUESTS,
54 code: "rate_limited",
55 message: "Too many reports. Try again later.".to_string(),
56 retry_after: Some(retry_after),
57 }
58 }
59
60 pub fn invalid_report(reason: &'static str) -> Self {
62 Self {
63 status: axum::http::StatusCode::BAD_REQUEST,
64 code: "invalid_report",
65 message: reason.to_string(),
66 retry_after: None,
67 }
68 }
69
70 pub fn internal(context: &str, error: impl std::fmt::Display) -> Self {
72 tracing::error!(error = %error, "{context}");
73 Self {
74 status: axum::http::StatusCode::INTERNAL_SERVER_ERROR,
75 code: "internal",
76 message: "Internal server error.".to_string(),
77 retry_after: None,
78 }
79 }
80}
81
82impl axum::response::IntoResponse for AppError {
83 fn into_response(self) -> axum::response::Response {
84 let body = pacenotes_shared::ApiError {
85 code: self.code.to_string(),
86 message: self.message,
87 };
88 let mut response = (self.status, axum::Json(body)).into_response();
89 if let Some(seconds) = self.retry_after {
90 if let Ok(value) = axum::http::HeaderValue::from_str(&seconds.to_string()) {
91 response
92 .headers_mut()
93 .insert(axum::http::header::RETRY_AFTER, value);
94 }
95 }
96 if self.status == axum::http::StatusCode::UNAUTHORIZED {
98 response.headers_mut().insert(
99 axum::http::header::WWW_AUTHENTICATE,
100 axum::http::HeaderValue::from_static("Bearer"),
101 );
102 }
103 response
104 }
105}