1use serde::{Deserialize, Serialize};
2
3#[derive(Deserialize)]
4pub(crate) struct ErrorResponse {
5 pub(crate) error: ApiError,
6}
7
8#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
9pub struct ApiError {
10 pub id: String,
11 pub name: String,
12 pub detail: String,
13}
14
15impl std::fmt::Display for ApiError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "({}) {} - {}", self.id, self.name, self.detail)
18 }
19}
20
21#[derive(Debug, thiserror::Error)]
22pub enum Error {
23 #[error("bad request: {0}")]
24 BadRequest(ApiError),
25 #[error("internal server error: {0}")]
26 InternalServerError(ApiError),
27 #[error("unauthorized: {0}")]
28 Unauthorized(ApiError),
29 #[error("rate limited: {0}")]
30 RateLimited(ApiError),
31 #[error("not found: {0}")]
32 NotFound(ApiError),
33 #[error("forbidden: {0}")]
34 Forbidden(ApiError),
35 #[error("conflict: {0}")]
36 Conflict(ApiError),
37 #[error("service unavailable: {0}")]
38 ServiceUnavailable(ApiError),
39 #[error("unknown error: {0}")]
40 UnknownError(ApiError),
41 #[error(transparent)]
42 Reqwest(#[from] reqwest::Error),
43 #[error(transparent)]
44 ParseError(#[from] url::ParseError),
45 #[error("invalid rate limit configuration: {0}")]
46 InvalidRateLimit(String),
47 #[error("OAuth Error: {0}")]
48 OAuth(String),
49}
50
51impl Error {
52 pub fn new_api_error(status: reqwest::StatusCode, api_error: ApiError) -> Self {
53 match status {
54 reqwest::StatusCode::BAD_REQUEST => Error::BadRequest(api_error),
55 reqwest::StatusCode::INTERNAL_SERVER_ERROR => Error::InternalServerError(api_error),
56 reqwest::StatusCode::UNAUTHORIZED => Error::Unauthorized(api_error),
57 reqwest::StatusCode::TOO_MANY_REQUESTS => Error::RateLimited(api_error),
58 reqwest::StatusCode::NOT_FOUND => Error::NotFound(api_error),
59 reqwest::StatusCode::FORBIDDEN => Error::Forbidden(api_error),
60 reqwest::StatusCode::CONFLICT => Error::Conflict(api_error),
61 reqwest::StatusCode::SERVICE_UNAVAILABLE => Error::ServiceUnavailable(api_error),
62 _ => Error::UnknownError(api_error),
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use reqwest::StatusCode;
71
72 fn api_error() -> ApiError {
73 ApiError {
74 id: "test".into(),
75 name: "test_error".into(),
76 detail: "something went
77 wrong"
78 .into(),
79 }
80 }
81
82 type StatusCase = (StatusCode, fn(&Error) -> bool);
83
84 #[test]
85 fn maps_status_codes_to_error_variants() {
86 let cases: &[StatusCase] = &[
87 (StatusCode::BAD_REQUEST, |e| {
88 matches!(e, Error::BadRequest(_))
89 }),
90 (StatusCode::UNAUTHORIZED, |e| {
91 matches!(e, Error::Unauthorized(_))
92 }),
93 (StatusCode::FORBIDDEN, |e| matches!(e, Error::Forbidden(_))),
94 (StatusCode::NOT_FOUND, |e| matches!(e, Error::NotFound(_))),
95 (StatusCode::CONFLICT, |e| matches!(e, Error::Conflict(_))),
96 (StatusCode::TOO_MANY_REQUESTS, |e| {
97 matches!(e, Error::RateLimited(_))
98 }),
99 (StatusCode::INTERNAL_SERVER_ERROR, |e| {
100 matches!(e, Error::InternalServerError(_))
101 }),
102 (StatusCode::SERVICE_UNAVAILABLE, |e| {
103 matches!(e, Error::ServiceUnavailable(_))
104 }),
105 (StatusCode::IM_A_TEAPOT, |e| {
106 matches!(e, Error::UnknownError(_))
107 }),
108 ];
109
110 for (status, check) in cases {
111 let err = Error::new_api_error(*status, api_error());
112 assert!(check(&err), "wrong variant for status {status}");
113 }
114 }
115}