mithril_aggregator/http_server/routes/
reply.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use std::path::Path;

use serde::Serialize;
use warp::http::StatusCode;

use mithril_common::entities::{ClientError, ServerError};
use mithril_common::StdError;
use mithril_persistence::sqlite::error::{SqliteError, SQLITE_BUSY};

use crate::tools::downcast_check;
use crate::SignerRegistrationError;

pub fn json<T>(value: &T, status_code: StatusCode) -> Box<dyn warp::Reply>
where
    T: Serialize,
{
    Box::new(warp::reply::with_status(
        warp::reply::json(value),
        status_code,
    ))
}

pub fn empty(status_code: StatusCode) -> Box<dyn warp::Reply> {
    Box::new(warp::reply::with_status(warp::reply::reply(), status_code))
}

pub fn bad_request(label: String, message: String) -> Box<dyn warp::Reply> {
    json(&ClientError::new(label, message), StatusCode::BAD_REQUEST)
}

pub fn server_error<E: Into<StdError>>(error: E) -> Box<dyn warp::Reply> {
    let std_error: StdError = error.into();
    let status_code = {
        let mut code = StatusCode::INTERNAL_SERVER_ERROR;

        if downcast_check::<SqliteError>(&std_error, |e| {
            e.code.is_some_and(|code| code == SQLITE_BUSY)
        }) {
            code = StatusCode::SERVICE_UNAVAILABLE;
        }

        if downcast_check::<SignerRegistrationError>(&std_error, |e| {
            matches!(e, SignerRegistrationError::RegistrationRoundNotYetOpened)
        }) {
            code = StatusCode::SERVICE_UNAVAILABLE;
        }

        code
    };

    json(&ServerError::new(format!("{std_error:?}")), status_code)
}

pub fn internal_server_error<T: Into<ServerError>>(message: T) -> Box<dyn warp::Reply> {
    json(&message.into(), StatusCode::INTERNAL_SERVER_ERROR)
}

pub fn service_unavailable<T: Into<ServerError>>(message: T) -> Box<dyn warp::Reply> {
    json(&message.into(), StatusCode::SERVICE_UNAVAILABLE)
}

pub fn add_content_disposition_header(
    reply: warp::fs::File,
    filepath: &Path,
) -> Box<dyn warp::Reply> {
    Box::new(warp::reply::with_header(
        reply,
        "Content-Disposition",
        format!(
            "attachment; filename=\"{}\"",
            filepath.file_name().unwrap().to_str().unwrap()
        ),
    ))
}

#[cfg(test)]
mod tests {
    use anyhow::anyhow;
    use warp::Reply;

    use super::*;

    #[test]
    fn test_server_error_convert_std_error_to_500_by_default() {
        let error = anyhow!("Some error");
        let response = server_error(error).into_response();

        assert_eq!(StatusCode::INTERNAL_SERVER_ERROR, response.status());
    }

    #[test]
    fn test_server_error_convert_wrapped_sqlite_busy_error_to_503() {
        let res = sqlite::Error {
            code: Some(SQLITE_BUSY),
            message: None,
        };
        let response = server_error(res).into_response();

        assert_eq!(StatusCode::SERVICE_UNAVAILABLE, response.status());

        // Wrapping the error in a StdError should also work
        let res = anyhow!(sqlite::Error {
            code: Some(SQLITE_BUSY),
            message: None,
        });
        let response = server_error(res).into_response();

        assert_eq!(StatusCode::SERVICE_UNAVAILABLE, response.status());
    }

    #[test]
    fn test_server_error_convert_signer_registration_round_not_yet_opened_to_503() {
        let err = SignerRegistrationError::RegistrationRoundNotYetOpened;
        let response = server_error(err).into_response();

        assert_eq!(StatusCode::SERVICE_UNAVAILABLE, response.status());

        // Wrapping the error in a StdError should also work
        let err = anyhow!(SignerRegistrationError::RegistrationRoundNotYetOpened);
        let response = server_error(err).into_response();

        assert_eq!(StatusCode::SERVICE_UNAVAILABLE, response.status());
    }
}