1use serde::Deserialize;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
17pub enum ApiError {
18 #[error("Bad Request: {0:?}")]
19 BadRequest(Vec<BadRequestError>),
20
21 #[error("Unauthorized")]
22 Unauthorized,
23
24 #[error("Not Found")]
25 NotFound,
26
27 #[error("Forbidden")]
28 Forbidden,
29
30 #[error("Internal Server Error: {0}")]
31 InternalServerError(String),
32
33 #[error("Method Not Allowed")]
34 MethodNotAllowed,
35
36 #[error("Too many requests")]
37 TooManyRequests,
38
39 #[error("Other Error: {0}")]
40 Other(String),
41
42 #[error("Client Error: {0}")]
43 Client(#[from] ClientError),
44}
45
46#[derive(Debug, Error)]
52pub enum ClientError {
53 #[error("Bad Gateway")]
54 BadGateway,
55 #[error("HTTP Request Error: {0}")]
56 HttpRequestError(#[from] reqwest::Error),
57
58 #[error("Service Unavailable")]
59 ServiceUnavailable,
60}
61
62#[derive(Error, Debug, Deserialize, PartialEq)]
64pub enum BadRequestError {
65 #[error("Invalid data")]
66 InvalidData,
67 #[error("Missing choices")]
68 MissingChoices,
69 #[error("Choice too long")]
70 ChoiceTooLong,
71 #[error("Too many choices")]
72 TooManyChoices,
73 #[error("Choice required")]
74 ChoiceRequired,
75 #[error("Ballot required")]
76 BallotRequired,
77 #[error("Voter ID too long")]
78 VoterIDTooLong,
79 #[error("Invalid voter ID")]
80 InvalidVoterID,
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::handle_api_response;
87 use http::response::Builder;
88 use reqwest::{Response, StatusCode};
89
90 impl PartialEq for ApiError {
91 fn eq(&self, other: &Self) -> bool {
92 match (self, other) {
93 (ApiError::BadRequest(errors_self), ApiError::BadRequest(errors_other)) => {
94 for err in errors_self {
95 if !errors_other.contains(err) {
96 return false;
97 }
98 }
99
100 true
101 }
102 (ApiError::Unauthorized, ApiError::Unauthorized) => true,
103 (ApiError::NotFound, ApiError::NotFound) => true,
104 (ApiError::Forbidden, ApiError::Forbidden) => true,
105 (
106 ApiError::InternalServerError(msg_self),
107 ApiError::InternalServerError(msg_other),
108 ) => msg_self == msg_other,
109 (ApiError::MethodNotAllowed, ApiError::MethodNotAllowed) => true,
110 (ApiError::TooManyRequests, ApiError::TooManyRequests) => true,
111 (ApiError::Other(msg_self), ApiError::Other(msg_other)) => msg_self == msg_other,
112 (ApiError::Client(err_self), ApiError::Client(err_other)) => {
113 match (err_self, err_other) {
114 (ClientError::BadGateway, ClientError::BadGateway) => true,
115 (ClientError::ServiceUnavailable, ClientError::ServiceUnavailable) => true,
116 (
117 ClientError::HttpRequestError(err_self),
118 ClientError::HttpRequestError(err_other),
119 ) => err_self.to_string() == err_other.to_string(),
120 _ => false,
121 }
122 }
123 _ => false,
124 }
125 }
126 }
127
128 fn create_mock_response(status: StatusCode, body: &str) -> Response {
129 let response = Builder::new()
130 .status(status)
131 .body(body.to_string())
132 .unwrap();
133 Response::from(response)
134 }
135
136 #[tokio::test]
137 async fn api_errors_test() {
138 let test_cases = vec![
139 (
140 StatusCode::BAD_GATEWAY,
141 "",
142 ApiError::Client(ClientError::BadGateway),
143 ),
144 (
145 StatusCode::SERVICE_UNAVAILABLE,
146 "",
147 ApiError::Client(ClientError::ServiceUnavailable),
148 ),
149 (StatusCode::BAD_REQUEST, "", ApiError::BadRequest(vec![])),
150 (
151 StatusCode::BAD_REQUEST,
152 r#"{"code":400,"message":"Bad Request","errors":["Invalid data"]}"#,
153 ApiError::BadRequest(vec![BadRequestError::InvalidData]),
154 ),
155 (
156 StatusCode::BAD_REQUEST,
157 r#"{"code":400,"message":"Bad Request","errors":["Missing choices"]}"#,
158 ApiError::BadRequest(vec![BadRequestError::MissingChoices]),
159 ),
160 (
161 StatusCode::BAD_REQUEST,
162 r#"{"code":400,"message":"Bad Request","errors":["Choice too long"]}"#,
163 ApiError::BadRequest(vec![BadRequestError::ChoiceTooLong]),
164 ),
165 (
166 StatusCode::BAD_REQUEST,
167 r#"{"code":400,"message":"Bad Request","errors":["Too many choices"]}"#,
168 ApiError::BadRequest(vec![BadRequestError::TooManyChoices]),
169 ),
170 (
171 StatusCode::BAD_REQUEST,
172 r#"{"code":400,"message":"Bad Request","errors":["Choice required"]}"#,
173 ApiError::BadRequest(vec![BadRequestError::ChoiceRequired]),
174 ),
175 (
176 StatusCode::BAD_REQUEST,
177 r#"{"code":400,"message":"Bad Request","errors":["Ballot required"]}"#,
178 ApiError::BadRequest(vec![BadRequestError::BallotRequired]),
179 ),
180 (
181 StatusCode::BAD_REQUEST,
182 r#"{"code":400,"message":"Bad Request","errors":["Voter ID too long"]}"#,
183 ApiError::BadRequest(vec![BadRequestError::VoterIDTooLong]),
184 ),
185 (
186 StatusCode::BAD_REQUEST,
187 r#"{"code":400,"message":"Bad Request","errors":["Invalid voter ID"]}"#,
188 ApiError::BadRequest(vec![BadRequestError::InvalidVoterID]),
189 ),
190 (
191 StatusCode::BAD_REQUEST,
192 r#"{"code":400,"message":"Bad Request","errors":["Invalid data","Missing choices"]}"#,
193 ApiError::BadRequest(vec![
194 BadRequestError::InvalidData,
195 BadRequestError::MissingChoices,
196 ]),
197 ),
198 (StatusCode::TOO_MANY_REQUESTS, "", ApiError::TooManyRequests),
199 (
200 StatusCode::INTERNAL_SERVER_ERROR,
201 "Internal Server Error",
202 ApiError::InternalServerError("Internal Server Error".to_string()),
203 ),
204 (
205 StatusCode::INTERNAL_SERVER_ERROR,
206 "",
207 ApiError::InternalServerError("".to_string()),
208 ),
209 (StatusCode::NOT_FOUND, "", ApiError::NotFound),
210 (StatusCode::UNAUTHORIZED, "", ApiError::Unauthorized),
211 (StatusCode::FORBIDDEN, "", ApiError::Forbidden),
212 (
213 StatusCode::METHOD_NOT_ALLOWED,
214 "",
215 ApiError::MethodNotAllowed,
216 ),
217 ];
218
219 for (status, body, expected_error) in test_cases {
220 let mock_response = create_mock_response(status, body);
221 let result = handle_api_response::<()>(mock_response).await;
222
223 match result {
224 Ok(_) => assert!(false, "Expected error but got Ok"),
225 Err(err) => assert_eq!(err, expected_error),
226 }
227 }
228 }
229}