usenet_dl/api/
error_response.rs1use crate::error::{ApiError, Error, ToHttpStatus};
7use axum::{
8 Json,
9 http::StatusCode,
10 response::{IntoResponse, Response},
11};
12
13impl IntoResponse for Error {
15 fn into_response(self) -> Response {
16 let status_code =
17 StatusCode::from_u16(self.status_code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
18
19 let api_error: ApiError = self.into();
20
21 (status_code, Json(api_error)).into_response()
22 }
23}
24
25impl IntoResponse for ApiError {
27 fn into_response(self) -> Response {
28 (StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
31 }
32}
33
34#[allow(clippy::unwrap_used, clippy::expect_used)]
36#[cfg(test)]
37mod tests {
38 use super::*;
39 use crate::error::{DatabaseError, DownloadError, PostProcessError};
40 use std::path::PathBuf;
41
42 #[test]
43 fn test_error_to_http_status_not_found() {
44 let error = Error::NotFound("test".to_string());
45 assert_eq!(error.status_code(), 404);
46 assert_eq!(error.error_code(), "not_found");
47 }
48
49 #[test]
50 fn test_error_to_http_status_download_not_found() {
51 let error = Error::Download(DownloadError::NotFound { id: 123 });
52 assert_eq!(error.status_code(), 404);
53 assert_eq!(error.error_code(), "download_not_found");
54 }
55
56 #[test]
57 fn test_error_to_http_status_conflict() {
58 let error = Error::Download(DownloadError::AlreadyInState {
59 id: 123,
60 state: "paused".to_string(),
61 });
62 assert_eq!(error.status_code(), 409);
63 assert_eq!(error.error_code(), "already_in_state");
64 }
65
66 #[test]
67 fn test_error_to_http_status_unprocessable() {
68 let error = Error::InvalidNzb("bad nzb".to_string());
69 assert_eq!(error.status_code(), 422);
70 assert_eq!(error.error_code(), "invalid_nzb");
71 }
72
73 #[test]
74 fn test_error_to_http_status_service_unavailable() {
75 let error = Error::ShuttingDown;
76 assert_eq!(error.status_code(), 503);
77 assert_eq!(error.error_code(), "shutting_down");
78 }
79
80 #[test]
81 fn test_error_to_http_status_internal_server() {
82 let error = Error::Database(DatabaseError::QueryFailed("query failed".to_string()));
83 assert_eq!(error.status_code(), 500);
84 assert_eq!(error.error_code(), "database_error");
85 }
86
87 #[test]
88 fn test_error_to_api_error_with_details() {
89 let error = Error::Download(DownloadError::NotFound { id: 123 });
90 let api_error: ApiError = error.into();
91
92 assert_eq!(api_error.error.code, "download_not_found");
93 assert!(api_error.error.message.contains("123"));
94 assert!(api_error.error.details.is_some());
95
96 let details = api_error.error.details.unwrap();
97 assert_eq!(details["download_id"], 123);
98 }
99
100 #[test]
101 fn test_error_to_api_error_insufficient_space() {
102 let error = Error::InsufficientSpace {
103 required: 1000,
104 available: 500,
105 };
106 let api_error: ApiError = error.into();
107
108 assert_eq!(api_error.error.code, "insufficient_space");
109 assert!(api_error.error.message.contains("1000"));
110 assert!(api_error.error.message.contains("500"));
111
112 let details = api_error.error.details.unwrap();
113 assert_eq!(details["required_bytes"], 1000);
114 assert_eq!(details["available_bytes"], 500);
115 }
116
117 #[test]
118 fn test_error_to_api_error_post_process() {
119 let error = Error::PostProcess(PostProcessError::WrongPassword {
120 archive: PathBuf::from("/path/to/archive.rar"),
121 });
122 let api_error: ApiError = error.into();
123
124 assert_eq!(api_error.error.code, "wrong_password");
125 assert!(api_error.error.details.is_some());
126
127 let details = api_error.error.details.unwrap();
128 assert!(details["archive"].as_str().unwrap().contains("archive.rar"));
129 }
130
131 #[tokio::test]
132 async fn test_error_into_response() {
133 let error = Error::NotFound("test resource".to_string());
134 let response = error.into_response();
135
136 assert_eq!(response.status(), StatusCode::NOT_FOUND);
137
138 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
140 .await
141 .unwrap();
142 let api_error: ApiError = serde_json::from_slice(&body).unwrap();
143
144 assert_eq!(api_error.error.code, "not_found");
145 assert!(api_error.error.message.contains("test resource"));
146 }
147
148 #[tokio::test]
149 async fn test_download_error_into_response() {
150 let error = Error::Download(DownloadError::AlreadyInState {
151 id: 456,
152 state: "downloading".to_string(),
153 });
154 let response = error.into_response();
155
156 assert_eq!(response.status(), StatusCode::CONFLICT);
157
158 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
159 .await
160 .unwrap();
161 let api_error: ApiError = serde_json::from_slice(&body).unwrap();
162
163 assert_eq!(api_error.error.code, "already_in_state");
164 assert_eq!(
165 api_error.error.details.as_ref().unwrap()["download_id"],
166 456
167 );
168 assert_eq!(
169 api_error.error.details.as_ref().unwrap()["state"],
170 "downloading"
171 );
172 }
173}