mithril_aggregator/http_server/routes/
certificate_routes.rs

1use warp::Filter;
2
3use crate::http_server::routes::middlewares;
4use crate::http_server::routes::router::RouterState;
5
6pub fn routes(
7    router_state: &RouterState,
8) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
9    certificate_certificates(router_state).or(certificate_certificate_hash(router_state))
10}
11
12/// GET /certificates
13fn certificate_certificates(
14    router_state: &RouterState,
15) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
16    warp::path!("certificates")
17        .and(warp::get())
18        .and(middlewares::with_logger(router_state))
19        .and(middlewares::with_http_message_service(router_state))
20        .and_then(handlers::certificate_certificates)
21}
22
23/// GET /certificate/{certificate_hash}
24fn certificate_certificate_hash(
25    router_state: &RouterState,
26) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
27    warp::path!("certificate" / String)
28        .and(warp::get())
29        .and(middlewares::with_logger(router_state))
30        .and(middlewares::with_http_message_service(router_state))
31        .and(middlewares::with_metrics_service(router_state))
32        .and_then(handlers::certificate_certificate_hash)
33}
34
35mod handlers {
36    use crate::MetricsService;
37    use crate::{http_server::routes::reply, services::MessageService};
38
39    use slog::{warn, Logger};
40    use std::convert::Infallible;
41    use std::sync::Arc;
42    use warp::http::StatusCode;
43
44    pub const LIST_MAX_ITEMS: usize = 20;
45
46    /// List all Certificates
47    pub async fn certificate_certificates(
48        logger: Logger,
49        http_message_service: Arc<dyn MessageService>,
50    ) -> Result<impl warp::Reply, Infallible> {
51        match http_message_service
52            .get_certificate_list_message(LIST_MAX_ITEMS)
53            .await
54        {
55            Ok(certificates) => Ok(reply::json(&certificates, StatusCode::OK)),
56            Err(err) => {
57                warn!(logger,"certificate_certificates::error"; "error" => ?err);
58                Ok(reply::server_error(err))
59            }
60        }
61    }
62
63    /// Certificate by certificate hash
64    pub async fn certificate_certificate_hash(
65        certificate_hash: String,
66        logger: Logger,
67        http_message_service: Arc<dyn MessageService>,
68        metrics_service: Arc<MetricsService>,
69    ) -> Result<impl warp::Reply, Infallible> {
70        metrics_service
71            .get_certificate_detail_total_served_since_startup()
72            .increment();
73
74        match http_message_service
75            .get_certificate_message(&certificate_hash)
76            .await
77        {
78            Ok(Some(certificate)) => Ok(reply::json(&certificate, StatusCode::OK)),
79            Ok(None) => Ok(reply::empty(StatusCode::NOT_FOUND)),
80            Err(err) => {
81                warn!(logger,"certificate_certificate_hash::error"; "error" => ?err);
82                Ok(reply::server_error(err))
83            }
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use anyhow::anyhow;
91    use mithril_common::test_utils::{apispec::APISpec, fake_data};
92    use serde_json::Value::Null;
93    use std::sync::Arc;
94    use warp::{
95        http::{Method, StatusCode},
96        test::request,
97    };
98
99    use crate::{initialize_dependencies, services::MockMessageService};
100
101    use super::*;
102
103    fn setup_router(
104        state: RouterState,
105    ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
106        let cors = warp::cors()
107            .allow_any_origin()
108            .allow_headers(vec!["content-type"])
109            .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
110
111        warp::any().and(routes(&state).with(cors))
112    }
113
114    #[tokio::test]
115    async fn test_certificate_certificates_get_ok() {
116        let dependency_manager = initialize_dependencies!().await;
117        dependency_manager
118            .certificate_repository
119            .create_certificate(fake_data::genesis_certificate("{certificate_hash}"))
120            .await
121            .expect("certificate store save should have succeeded");
122
123        let method = Method::GET.as_str();
124        let path = "/certificates";
125
126        let response = request()
127            .method(method)
128            .path(path)
129            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
130                dependency_manager,
131            ))))
132            .await;
133
134        APISpec::verify_conformity(
135            APISpec::get_all_spec_files(),
136            method,
137            path,
138            "application/json",
139            &Null,
140            &response,
141            &StatusCode::OK,
142        )
143        .unwrap();
144    }
145
146    #[tokio::test]
147    async fn test_certificate_when_error_retrieving_certificates_returns_ko_500() {
148        let mut dependency_manager = initialize_dependencies!().await;
149        let mut message_service = MockMessageService::new();
150        message_service
151            .expect_get_certificate_list_message()
152            .returning(|_| Err(anyhow!("an error")));
153        dependency_manager.message_service = Arc::new(message_service);
154
155        let method = Method::GET.as_str();
156        let path = "/certificates";
157
158        let response = request()
159            .method(method)
160            .path(path)
161            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
162                dependency_manager,
163            ))))
164            .await;
165
166        APISpec::verify_conformity(
167            APISpec::get_all_spec_files(),
168            method,
169            path,
170            "application/json",
171            &Null,
172            &response,
173            &StatusCode::INTERNAL_SERVER_ERROR,
174        )
175        .unwrap();
176    }
177
178    #[tokio::test]
179    async fn test_certificate_certificate_hash_increments_certificate_detail_total_served_since_startup_metric(
180    ) {
181        let method = Method::GET.as_str();
182        let path = "/certificate/{certificate_hash}";
183        let dependency_manager = Arc::new(initialize_dependencies!().await);
184        let initial_counter_value = dependency_manager
185            .metrics_service
186            .get_certificate_detail_total_served_since_startup()
187            .get();
188
189        request()
190            .method(method)
191            .path(path)
192            .reply(&setup_router(RouterState::new_with_dummy_config(
193                dependency_manager.clone(),
194            )))
195            .await;
196
197        assert_eq!(
198            initial_counter_value + 1,
199            dependency_manager
200                .metrics_service
201                .get_certificate_detail_total_served_since_startup()
202                .get()
203        );
204    }
205
206    #[tokio::test]
207    async fn test_certificate_certificate_hash_get_ok() {
208        let dependency_manager = initialize_dependencies!().await;
209        dependency_manager
210            .certificate_repository
211            .create_certificate(fake_data::genesis_certificate("{certificate_hash}"))
212            .await
213            .expect("certificate store save should have succeeded");
214
215        let method = Method::GET.as_str();
216        let path = "/certificate/{certificate_hash}";
217
218        let response = request()
219            .method(method)
220            .path(path)
221            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
222                dependency_manager,
223            ))))
224            .await;
225
226        APISpec::verify_conformity(
227            APISpec::get_all_spec_files(),
228            method,
229            path,
230            "application/json",
231            &Null,
232            &response,
233            &StatusCode::OK,
234        )
235        .unwrap();
236    }
237
238    #[tokio::test]
239    async fn test_certificate_certificate_hash_get_ok_404() {
240        let dependency_manager = initialize_dependencies!().await;
241
242        let method = Method::GET.as_str();
243        let path = "/certificate/{certificate_hash}";
244
245        let response = request()
246            .method(method)
247            .path(path)
248            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
249                dependency_manager,
250            ))))
251            .await;
252
253        APISpec::verify_conformity(
254            APISpec::get_all_spec_files(),
255            method,
256            path,
257            "application/json",
258            &Null,
259            &response,
260            &StatusCode::NOT_FOUND,
261        )
262        .unwrap();
263    }
264
265    #[tokio::test]
266    async fn test_certificate_when_error_on_retrieving_certificate_hash_returns_ko_500() {
267        let mut dependency_manager = initialize_dependencies!().await;
268        let mut message_service = MockMessageService::new();
269        message_service
270            .expect_get_certificate_message()
271            .returning(|_| Err(anyhow!("an error")));
272        dependency_manager.message_service = Arc::new(message_service);
273
274        let method = Method::GET.as_str();
275        let path = "/certificate/{certificate_hash}";
276
277        let response = request()
278            .method(method)
279            .path(&path.replace("{certificate_hash}", "whatever"))
280            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
281                dependency_manager,
282            ))))
283            .await;
284
285        APISpec::verify_conformity(
286            APISpec::get_all_spec_files(),
287            method,
288            path,
289            "application/json",
290            &Null,
291            &response,
292            &StatusCode::INTERNAL_SERVER_ERROR,
293        )
294        .unwrap();
295    }
296}