mithril_aggregator/http_server/routes/
reply.rs

1use std::path::Path;
2
3use serde::Serialize;
4use warp::http::StatusCode;
5
6use mithril_common::entities::{ClientError, ServerError};
7use mithril_common::StdError;
8use mithril_persistence::sqlite::error::{SqliteError, SQLITE_BUSY};
9
10use crate::tools::downcast_check;
11use crate::SignerRegistrationError;
12
13pub struct MithrilStatusCode();
14
15impl MithrilStatusCode {
16    pub fn registration_round_not_yet_opened() -> StatusCode {
17        // The unwrap is safe here because `from_16` function return error only for values outside of the range 100-999,
18        StatusCode::from_u16(550).unwrap()
19    }
20}
21
22pub fn json<T>(value: &T, status_code: StatusCode) -> Box<dyn warp::Reply>
23where
24    T: Serialize,
25{
26    Box::new(warp::reply::with_status(
27        warp::reply::json(value),
28        status_code,
29    ))
30}
31
32pub fn empty(status_code: StatusCode) -> Box<dyn warp::Reply> {
33    Box::new(warp::reply::with_status(warp::reply::reply(), status_code))
34}
35
36pub fn bad_request(label: String, message: String) -> Box<dyn warp::Reply> {
37    json(&ClientError::new(label, message), StatusCode::BAD_REQUEST)
38}
39
40pub fn gone(label: String, message: String) -> Box<dyn warp::Reply> {
41    json(&ClientError::new(label, message), StatusCode::GONE)
42}
43
44pub fn server_error<E: Into<StdError>>(error: E) -> Box<dyn warp::Reply> {
45    let std_error: StdError = error.into();
46    let status_code = {
47        let mut code = StatusCode::INTERNAL_SERVER_ERROR;
48
49        if downcast_check::<SqliteError>(&std_error, |e| {
50            e.code.is_some_and(|code| code == SQLITE_BUSY)
51        }) {
52            code = StatusCode::SERVICE_UNAVAILABLE;
53        }
54
55        if downcast_check::<SignerRegistrationError>(&std_error, |e| {
56            matches!(e, SignerRegistrationError::RegistrationRoundNotYetOpened)
57        }) {
58            code = MithrilStatusCode::registration_round_not_yet_opened();
59        }
60
61        code
62    };
63
64    json(&ServerError::new(format!("{std_error:?}")), status_code)
65}
66
67pub fn internal_server_error<T: Into<ServerError>>(message: T) -> Box<dyn warp::Reply> {
68    json(&message.into(), StatusCode::INTERNAL_SERVER_ERROR)
69}
70
71pub fn add_content_disposition_header(
72    reply: warp::fs::File,
73    filepath: &Path,
74) -> Box<dyn warp::Reply> {
75    Box::new(warp::reply::with_header(
76        reply,
77        "Content-Disposition",
78        format!(
79            "attachment; filename=\"{}\"",
80            filepath.file_name().unwrap().to_str().unwrap()
81        ),
82    ))
83}
84
85#[cfg(test)]
86mod tests {
87    use anyhow::anyhow;
88    use warp::Reply;
89
90    use super::*;
91
92    #[test]
93    fn test_server_error_convert_std_error_to_500_by_default() {
94        let error = anyhow!("Some error");
95        let response = server_error(error).into_response();
96
97        assert_eq!(StatusCode::INTERNAL_SERVER_ERROR, response.status());
98    }
99
100    #[test]
101    fn test_server_error_convert_wrapped_sqlite_busy_error_to_503() {
102        let res = sqlite::Error {
103            code: Some(SQLITE_BUSY),
104            message: None,
105        };
106        let response = server_error(res).into_response();
107
108        assert_eq!(StatusCode::SERVICE_UNAVAILABLE, response.status());
109
110        // Wrapping the error in a StdError should also work
111        let res = anyhow!(sqlite::Error {
112            code: Some(SQLITE_BUSY),
113            message: None,
114        });
115        let response = server_error(res).into_response();
116
117        assert_eq!(StatusCode::SERVICE_UNAVAILABLE, response.status());
118    }
119
120    #[test]
121    fn test_server_error_convert_signer_registration_round_not_yet_opened_to_550() {
122        let err = SignerRegistrationError::RegistrationRoundNotYetOpened;
123        let response = server_error(err).into_response();
124
125        assert_eq!(
126            MithrilStatusCode::registration_round_not_yet_opened(),
127            response.status()
128        );
129
130        // Wrapping the error in a StdError should also work
131        let err = anyhow!(SignerRegistrationError::RegistrationRoundNotYetOpened);
132        let response = server_error(err).into_response();
133
134        assert_eq!(
135            MithrilStatusCode::registration_round_not_yet_opened(),
136            response.status()
137        );
138    }
139}