mithril_aggregator/http_server/routes/
certificate_routes.rs1use 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 + use<>,), Error = warp::Rejection> + Clone + use<> {
9 certificate_certificates(router_state)
10 .or(certificate_genesis(router_state))
11 .or(certificate_certificate_hash(router_state))
12}
13
14fn certificate_certificates(
16 router_state: &RouterState,
17) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
18 warp::path!("certificates")
19 .and(warp::get())
20 .and(middlewares::with_logger(router_state))
21 .and(middlewares::with_http_message_service(router_state))
22 .and_then(handlers::certificate_certificates)
23}
24
25fn certificate_genesis(
27 router_state: &RouterState,
28) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
29 warp::path!("certificate" / "genesis")
30 .and(warp::get())
31 .and(middlewares::with_client_metadata(router_state))
32 .and(middlewares::with_logger(router_state))
33 .and(middlewares::with_http_message_service(router_state))
34 .and(middlewares::with_metrics_service(router_state))
35 .and_then(handlers::certificate_genesis)
36}
37
38fn certificate_certificate_hash(
40 router_state: &RouterState,
41) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
42 warp::path!("certificate" / String)
43 .and(warp::get())
44 .and(middlewares::with_client_metadata(router_state))
45 .and(middlewares::with_logger(router_state))
46 .and(middlewares::with_http_message_service(router_state))
47 .and(middlewares::with_metrics_service(router_state))
48 .and_then(handlers::certificate_certificate_hash)
49}
50
51mod handlers {
52 use slog::{Logger, warn};
53 use std::convert::Infallible;
54 use std::sync::Arc;
55 use warp::http::StatusCode;
56
57 use crate::MetricsService;
58 use crate::http_server::routes::middlewares::ClientMetadata;
59 use crate::{http_server::routes::reply, services::MessageService};
60
61 pub const LIST_MAX_ITEMS: usize = 20;
62
63 pub async fn certificate_certificates(
65 logger: Logger,
66 http_message_service: Arc<dyn MessageService>,
67 ) -> Result<impl warp::Reply, Infallible> {
68 match http_message_service
69 .get_certificate_list_message(LIST_MAX_ITEMS)
70 .await
71 {
72 Ok(certificates) => Ok(reply::json(&certificates, StatusCode::OK)),
73 Err(err) => {
74 warn!(logger,"certificate_certificates::error"; "error" => ?err);
75 Ok(reply::server_error(err))
76 }
77 }
78 }
79
80 pub async fn certificate_genesis(
82 client_metadata: ClientMetadata,
83 logger: Logger,
84 http_message_service: Arc<dyn MessageService>,
85 metrics_service: Arc<MetricsService>,
86 ) -> Result<impl warp::Reply, Infallible> {
87 metrics_service
88 .get_certificate_detail_total_served_since_startup()
89 .increment(&[
90 client_metadata.origin_tag.as_deref().unwrap_or_default(),
91 client_metadata.client_type.as_deref().unwrap_or_default(),
92 ]);
93
94 match http_message_service.get_latest_genesis_certificate_message().await {
95 Ok(Some(certificate)) => Ok(reply::json(&certificate, StatusCode::OK)),
96 Ok(None) => Ok(reply::empty(StatusCode::NOT_FOUND)),
97 Err(err) => {
98 warn!(logger,"certificate_certificate_genesis::error"; "error" => ?err);
99 Ok(reply::server_error(err))
100 }
101 }
102 }
103
104 pub async fn certificate_certificate_hash(
106 certificate_hash: String,
107 client_metadata: ClientMetadata,
108 logger: Logger,
109 http_message_service: Arc<dyn MessageService>,
110 metrics_service: Arc<MetricsService>,
111 ) -> Result<impl warp::Reply, Infallible> {
112 metrics_service
113 .get_certificate_detail_total_served_since_startup()
114 .increment(&[
115 client_metadata.origin_tag.as_deref().unwrap_or_default(),
116 client_metadata.client_type.as_deref().unwrap_or_default(),
117 ]);
118
119 match http_message_service.get_certificate_message(&certificate_hash).await {
120 Ok(Some(certificate)) => Ok(reply::json(&certificate, StatusCode::OK)),
121 Ok(None) => Ok(reply::empty(StatusCode::NOT_FOUND)),
122 Err(err) => {
123 warn!(logger,"certificate_certificate_hash::error"; "error" => ?err);
124 Ok(reply::server_error(err))
125 }
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use anyhow::anyhow;
133 use serde_json::Value::Null;
134 use std::sync::Arc;
135 use warp::{
136 http::{Method, StatusCode},
137 test::request,
138 };
139
140 use mithril_api_spec::APISpec;
141 use mithril_common::{
142 MITHRIL_CLIENT_TYPE_HEADER, MITHRIL_ORIGIN_TAG_HEADER, messages::CertificateMessage,
143 test_utils::fake_data,
144 };
145
146 use crate::{initialize_dependencies, services::MockMessageService};
147
148 use super::*;
149
150 fn setup_router(
151 state: RouterState,
152 ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
153 let cors = warp::cors()
154 .allow_any_origin()
155 .allow_headers(vec!["content-type"])
156 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
157
158 warp::any().and(routes(&state).with(cors))
159 }
160
161 #[tokio::test]
162 async fn test_certificate_certificates_get_ok() {
163 let dependency_manager = initialize_dependencies!().await;
164 dependency_manager
165 .certificate_repository
166 .create_certificate(fake_data::genesis_certificate("{certificate_hash}"))
167 .await
168 .expect("certificate store save should have succeeded");
169
170 let method = Method::GET.as_str();
171 let path = "/certificates";
172
173 let response = request()
174 .method(method)
175 .path(path)
176 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
177 dependency_manager,
178 ))))
179 .await;
180
181 APISpec::verify_conformity(
182 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
183 method,
184 path,
185 "application/json",
186 &Null,
187 &response,
188 &StatusCode::OK,
189 )
190 .unwrap();
191 }
192
193 #[tokio::test]
194 async fn test_certificate_when_error_retrieving_certificates_returns_ko_500() {
195 let mut dependency_manager = initialize_dependencies!().await;
196 let mut message_service = MockMessageService::new();
197 message_service
198 .expect_get_certificate_list_message()
199 .returning(|_| Err(anyhow!("an error")));
200 dependency_manager.message_service = Arc::new(message_service);
201
202 let method = Method::GET.as_str();
203 let path = "/certificates";
204
205 let response = request()
206 .method(method)
207 .path(path)
208 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
209 dependency_manager,
210 ))))
211 .await;
212
213 APISpec::verify_conformity(
214 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
215 method,
216 path,
217 "application/json",
218 &Null,
219 &response,
220 &StatusCode::INTERNAL_SERVER_ERROR,
221 )
222 .unwrap();
223 }
224
225 #[tokio::test]
226 async fn test_certificate_certificate_hash_increments_certificate_detail_total_served_since_startup_metric()
227 {
228 let method = Method::GET.as_str();
229 let path = "/certificate/{certificate_hash}";
230
231 let dependency_manager = Arc::new(initialize_dependencies!().await);
232 let metric = dependency_manager
233 .metrics_service
234 .get_certificate_detail_total_served_since_startup();
235 let initial_counter_value_with_tag = metric.get(&["TEST", "CLI"]);
236 let initial_counter_value_with_unknown_tags =
237 metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"]);
238
239 request()
240 .method(method)
241 .path(path)
242 .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
243 .header(MITHRIL_CLIENT_TYPE_HEADER, "CLI")
244 .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
245 dependency_manager.clone(),
246 &["TEST"],
247 )))
248 .await;
249
250 assert_eq!(
251 initial_counter_value_with_tag + 1,
252 metric.get(&["TEST", "CLI"])
253 );
254 assert_eq!(
255 initial_counter_value_with_unknown_tags,
256 metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"])
257 );
258 }
259
260 #[tokio::test]
261 async fn test_certificate_certificate_hash_get_ok() {
262 let dependency_manager = initialize_dependencies!().await;
263 dependency_manager
264 .certificate_repository
265 .create_certificate(fake_data::genesis_certificate("{certificate_hash}"))
266 .await
267 .expect("certificate store save should have succeeded");
268
269 let method = Method::GET.as_str();
270 let path = "/certificate/{certificate_hash}";
271
272 let response = request()
273 .method(method)
274 .path(path)
275 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
276 dependency_manager,
277 ))))
278 .await;
279
280 APISpec::verify_conformity(
281 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
282 method,
283 path,
284 "application/json",
285 &Null,
286 &response,
287 &StatusCode::OK,
288 )
289 .unwrap();
290 }
291
292 #[tokio::test]
293 async fn test_certificate_certificate_hash_get_ko_404() {
294 let dependency_manager = initialize_dependencies!().await;
295
296 let method = Method::GET.as_str();
297 let path = "/certificate/{certificate_hash}";
298
299 let response = request()
300 .method(method)
301 .path(path)
302 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
303 dependency_manager,
304 ))))
305 .await;
306
307 APISpec::verify_conformity(
308 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
309 method,
310 path,
311 "application/json",
312 &Null,
313 &response,
314 &StatusCode::NOT_FOUND,
315 )
316 .unwrap();
317 }
318
319 #[tokio::test]
320 async fn test_certificate_when_error_on_retrieving_certificate_hash_returns_ko_500() {
321 let mut dependency_manager = initialize_dependencies!().await;
322 let mut message_service = MockMessageService::new();
323 message_service
324 .expect_get_certificate_message()
325 .returning(|_| Err(anyhow!("an error")));
326 dependency_manager.message_service = Arc::new(message_service);
327
328 let method = Method::GET.as_str();
329 let path = "/certificate/{certificate_hash}";
330
331 let response = request()
332 .method(method)
333 .path(&path.replace("{certificate_hash}", "whatever"))
334 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
335 dependency_manager,
336 ))))
337 .await;
338
339 APISpec::verify_conformity(
340 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
341 method,
342 path,
343 "application/json",
344 &Null,
345 &response,
346 &StatusCode::INTERNAL_SERVER_ERROR,
347 )
348 .unwrap();
349 }
350
351 #[tokio::test]
352 async fn test_certificate_genesis_increments_certificate_detail_total_served_since_startup_metric()
353 {
354 let method = Method::GET.as_str();
355 let path = "/certificate/genesis";
356
357 let dependency_manager = Arc::new(initialize_dependencies!().await);
358 let metric = dependency_manager
359 .metrics_service
360 .get_certificate_detail_total_served_since_startup();
361 let initial_counter_value_with_tag = metric.get(&["TEST", "CLI"]);
362 let initial_counter_value_with_unknown_tags =
363 metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"]);
364
365 request()
366 .method(method)
367 .path(path)
368 .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
369 .header(MITHRIL_CLIENT_TYPE_HEADER, "CLI")
370 .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
371 dependency_manager.clone(),
372 &["TEST"],
373 )))
374 .await;
375
376 assert_eq!(
377 initial_counter_value_with_tag + 1,
378 metric.get(&["TEST", "CLI"])
379 );
380 assert_eq!(
381 initial_counter_value_with_unknown_tags,
382 metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"])
383 );
384 }
385
386 #[tokio::test]
387 async fn test_certificate_genesis_get_ok() {
388 let dependency_manager = initialize_dependencies!().await;
389 dependency_manager
390 .certificate_repository
391 .create_certificate(fake_data::genesis_certificate("certificate_genesis_1"))
392 .await
393 .expect("certificate store save should have succeeded");
394 dependency_manager
395 .certificate_repository
396 .create_certificate(fake_data::genesis_certificate("certificate_genesis_2"))
397 .await
398 .expect("certificate store save should have succeeded");
399
400 let method = Method::GET.as_str();
401 let path = "/certificate/genesis";
402
403 let response = request()
404 .method(method)
405 .path(path)
406 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
407 dependency_manager,
408 ))))
409 .await;
410
411 APISpec::verify_conformity(
412 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
413 method,
414 path,
415 "application/json",
416 &Null,
417 &response,
418 &StatusCode::OK,
419 )
420 .unwrap();
421
422 let returned_genesis: CertificateMessage = serde_json::from_slice(response.body()).unwrap();
423 assert_eq!("certificate_genesis_2", &returned_genesis.hash);
424 }
425
426 #[tokio::test]
427 async fn test_certificate_genesis_get_ko_404() {
428 let dependency_manager = initialize_dependencies!().await;
429
430 let method = Method::GET.as_str();
431 let path = "/certificate/genesis";
432
433 let response = request()
434 .method(method)
435 .path(path)
436 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
437 dependency_manager,
438 ))))
439 .await;
440
441 APISpec::verify_conformity(
442 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
443 method,
444 path,
445 "application/json",
446 &Null,
447 &response,
448 &StatusCode::NOT_FOUND,
449 )
450 .unwrap();
451 }
452
453 #[tokio::test]
454 async fn test_certificate_when_error_on_retrieving_certificate_genesis_returns_ko_500() {
455 let mut dependency_manager = initialize_dependencies!().await;
456 let mut message_service = MockMessageService::new();
457 message_service
458 .expect_get_latest_genesis_certificate_message()
459 .returning(|| Err(anyhow!("an error")));
460 dependency_manager.message_service = Arc::new(message_service);
461
462 let method = Method::GET.as_str();
463 let path = "/certificate/genesis";
464
465 let response = request()
466 .method(method)
467 .path(path)
468 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
469 dependency_manager,
470 ))))
471 .await;
472
473 APISpec::verify_conformity(
474 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
475 method,
476 path,
477 "application/json",
478 &Null,
479 &response,
480 &StatusCode::INTERNAL_SERVER_ERROR,
481 )
482 .unwrap();
483 }
484}