]> Untitled Git - lemmy.git/blob - crates/utils/src/response.rs
Cache & Optimize Woodpecker CI (#3450)
[lemmy.git] / crates / utils / src / response.rs
1 use crate::error::{LemmyError, LemmyErrorType};
2 use actix_web::{
3   dev::ServiceResponse,
4   http::header,
5   middleware::ErrorHandlerResponse,
6   HttpResponse,
7 };
8
9 pub fn jsonify_plain_text_errors<BODY>(
10   res: ServiceResponse<BODY>,
11 ) -> actix_web::Result<ErrorHandlerResponse<BODY>> {
12   let maybe_error = res.response().error();
13
14   // This function is only expected to be called for errors, so if there is no error, return
15   if maybe_error.is_none() {
16     return Ok(ErrorHandlerResponse::Response(res.map_into_left_body()));
17   }
18   // We're assuming that any LemmyError is already in JSON format, so we don't need to do anything
19   if maybe_error
20     .expect("http responses with 400-599 statuses should have an error object")
21     .as_error::<LemmyError>()
22     .is_some()
23   {
24     return Ok(ErrorHandlerResponse::Response(res.map_into_left_body()));
25   }
26
27   let (req, res) = res.into_parts();
28   let error = res
29     .error()
30     .expect("expected an error object in the response");
31   let response = HttpResponse::build(res.status())
32     .append_header(header::ContentType::json())
33     .json(LemmyErrorType::Unknown(error.to_string()));
34
35   let service_response = ServiceResponse::new(req, response);
36   Ok(ErrorHandlerResponse::Response(
37     service_response.map_into_right_body(),
38   ))
39 }
40
41 #[cfg(test)]
42 mod tests {
43   use super::*;
44   use crate::error::{LemmyError, LemmyErrorType};
45   use actix_web::{
46     error::ErrorInternalServerError,
47     middleware::ErrorHandlers,
48     test,
49     web,
50     App,
51     Error,
52     Handler,
53     Responder,
54   };
55   use http::StatusCode;
56
57   #[actix_web::test]
58   async fn test_non_error_responses_are_not_modified() {
59     async fn ok_service() -> actix_web::Result<String, Error> {
60       Ok("Oll Korrect".to_string())
61     }
62
63     check_for_jsonification(ok_service, StatusCode::OK, "Oll Korrect").await;
64   }
65
66   #[actix_web::test]
67   async fn test_lemmy_errors_are_not_modified() {
68     async fn lemmy_error_service() -> actix_web::Result<String, LemmyError> {
69       Err(LemmyError::from(LemmyErrorType::EmailAlreadyExists))
70     }
71
72     check_for_jsonification(
73       lemmy_error_service,
74       StatusCode::BAD_REQUEST,
75       "{\"error\":\"email_already_exists\"}",
76     )
77     .await;
78   }
79
80   #[actix_web::test]
81   async fn test_generic_errors_are_jsonified_as_unknown_errors() {
82     async fn generic_error_service() -> actix_web::Result<String, Error> {
83       Err(ErrorInternalServerError("This is not a LemmyError"))
84     }
85
86     check_for_jsonification(
87       generic_error_service,
88       StatusCode::INTERNAL_SERVER_ERROR,
89       "{\"error\":\"unknown\",\"message\":\"This is not a LemmyError\"}",
90     )
91     .await;
92   }
93
94   #[actix_web::test]
95   async fn test_anyhow_errors_wrapped_in_lemmy_errors_are_jsonified_correctly() {
96     async fn anyhow_error_service() -> actix_web::Result<String, LemmyError> {
97       Err(LemmyError::from(anyhow::anyhow!("This is the inner error")))
98     }
99
100     check_for_jsonification(
101       anyhow_error_service,
102       StatusCode::BAD_REQUEST,
103       "{\"error\":\"unknown\",\"message\":\"This is the inner error\"}",
104     )
105     .await;
106   }
107
108   async fn check_for_jsonification(
109     service: impl Handler<(), Output = impl Responder + 'static>,
110     expected_status_code: StatusCode,
111     expected_body: &str,
112   ) {
113     let app = test::init_service(
114       App::new()
115         .wrap(ErrorHandlers::new().default_handler(jsonify_plain_text_errors))
116         .route("/", web::get().to(service)),
117     )
118     .await;
119     let req = test::TestRequest::default().to_request();
120     let res = test::call_service(&app, req).await;
121
122     assert_eq!(res.status(), expected_status_code);
123
124     let body = test::read_body(res).await;
125     assert_eq!(body, expected_body);
126   }
127 }