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
125
126
127
128
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 gone(label: String, message: String) -> Box<dyn warp::Reply> {
    json(&ClientError::new(label, message), StatusCode::GONE)
}

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());
    }
}