mithril_aggregator/http_server/routes/artifact_routes/
cardano_database.rs1use crate::http_server::routes::middlewares;
2use crate::http_server::routes::router::RouterState;
3use warp::Filter;
4
5pub fn routes(
6 router_state: &RouterState,
7) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
8 artifact_cardano_database_list(router_state)
9 .or(artifact_cardano_database_digest_list(router_state))
10 .or(artifact_cardano_database_by_id(router_state))
11 .or(serve_cardano_database_dir(router_state))
12}
13
14fn artifact_cardano_database_list(
16 router_state: &RouterState,
17) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
18 warp::path!("artifact" / "cardano-database")
19 .and(warp::get())
20 .and(middlewares::with_logger(router_state))
21 .and(middlewares::with_http_message_service(router_state))
22 .and_then(handlers::list_artifacts)
23}
24
25fn artifact_cardano_database_by_id(
27 dependency_manager: &RouterState,
28) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
29 warp::path!("artifact" / "cardano-database" / String)
30 .and(warp::get())
31 .and(middlewares::with_client_metadata(dependency_manager))
32 .and(middlewares::with_logger(dependency_manager))
33 .and(middlewares::with_http_message_service(dependency_manager))
34 .and(middlewares::with_metrics_service(dependency_manager))
35 .and_then(handlers::get_artifact_by_signed_entity_id)
36}
37
38fn artifact_cardano_database_digest_list(
40 router_state: &RouterState,
41) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
42 warp::path!("artifact" / "cardano-database" / "digests")
43 .and(warp::get())
44 .and(middlewares::with_logger(router_state))
45 .and(middlewares::with_http_message_service(router_state))
46 .and_then(handlers::list_digests)
47}
48
49fn serve_cardano_database_dir(
50 router_state: &RouterState,
51) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
52 warp::path(crate::http_server::CARDANO_DATABASE_DOWNLOAD_PATH)
53 .and(warp::fs::dir(
54 router_state
55 .configuration
56 .cardano_db_artifacts_directory
57 .clone(),
58 ))
59 .and(middlewares::with_logger(router_state))
60 .and(middlewares::extract_config(router_state, |config| {
61 config.allow_http_serve_directory
62 }))
63 .and_then(handlers::ensure_downloaded_file_is_a_cardano_database_artifact)
64}
65
66mod handlers {
67 use slog::{debug, warn, Logger};
68 use std::convert::Infallible;
69 use std::sync::Arc;
70 use warp::http::StatusCode;
71
72 use crate::http_server::routes::middlewares::ClientMetadata;
73 use crate::http_server::routes::reply;
74 use crate::services::MessageService;
75 use crate::MetricsService;
76
77 pub const LIST_MAX_ITEMS: usize = 20;
78
79 pub async fn list_artifacts(
81 logger: Logger,
82 http_message_service: Arc<dyn MessageService>,
83 ) -> Result<impl warp::Reply, Infallible> {
84 match http_message_service
85 .get_cardano_database_list_message(LIST_MAX_ITEMS)
86 .await
87 {
88 Ok(message) => Ok(reply::json(&message, StatusCode::OK)),
89 Err(err) => {
90 warn!(logger,"list_artifacts_cardano_database"; "error" => ?err);
91 Ok(reply::server_error(err))
92 }
93 }
94 }
95
96 pub async fn get_artifact_by_signed_entity_id(
98 signed_entity_id: String,
99 client_metadata: ClientMetadata,
100 logger: Logger,
101 http_message_service: Arc<dyn MessageService>,
102 metrics_service: Arc<MetricsService>,
103 ) -> Result<impl warp::Reply, Infallible> {
104 metrics_service
105 .get_artifact_detail_cardano_database_total_served_since_startup()
106 .increment(&[
107 client_metadata.origin_tag.as_deref().unwrap_or_default(),
108 client_metadata.client_type.as_deref().unwrap_or_default(),
109 ]);
110
111 match http_message_service
112 .get_cardano_database_message(&signed_entity_id)
113 .await
114 {
115 Ok(Some(signed_entity)) => Ok(reply::json(&signed_entity, StatusCode::OK)),
116 Ok(None) => {
117 warn!(logger, "cardano_database_details::not_found");
118 Ok(reply::empty(StatusCode::NOT_FOUND))
119 }
120 Err(err) => {
121 warn!(logger,"cardano_database_details::error"; "error" => ?err);
122 Ok(reply::server_error(err))
123 }
124 }
125 }
126
127 pub async fn ensure_downloaded_file_is_a_cardano_database_artifact(
130 reply: warp::fs::File,
131 logger: Logger,
132 allow_http_serve_directory: bool,
133 ) -> Result<impl warp::Reply, Infallible> {
134 let filepath = reply.path().to_path_buf();
135 debug!(
136 logger,
137 ">> ensure_downloaded_file_is_a_cardano_database / file: `{}`",
138 filepath.display()
139 );
140
141 if !allow_http_serve_directory {
142 warn!(logger, "ensure_downloaded_file_is_a_cardano_database::error"; "error" => "http serve directory is disabled");
143 return Ok(reply::empty(StatusCode::FORBIDDEN));
144 }
145
146 let file_is_a_cardano_database_archive = filepath.to_string_lossy().contains("ancillary")
148 || filepath.to_string_lossy().contains("immutable")
149 || filepath.to_string_lossy().contains("digests");
150 match file_is_a_cardano_database_archive {
151 true => Ok(reply::add_content_disposition_header(reply, &filepath)),
152 false => {
153 warn!(logger,"ensure_downloaded_file_is_a_cardano_database::error"; "error" => "file is not a Cardano database archive");
154 Ok(reply::empty(StatusCode::NOT_FOUND))
155 }
156 }
157 }
158
159 pub async fn list_digests(
161 logger: Logger,
162 http_message_service: Arc<dyn MessageService>,
163 ) -> Result<impl warp::Reply, Infallible> {
164 match http_message_service
165 .get_cardano_database_digest_list_message()
166 .await
167 {
168 Ok(message) => Ok(reply::json(&message, StatusCode::OK)),
169 Err(err) => {
170 warn!(logger,"list_digests_cardano_database"; "error" => ?err);
171 Ok(reply::server_error(err))
172 }
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use serde_json::Value::Null;
180 use std::sync::Arc;
181 use warp::{
182 http::{Method, StatusCode},
183 test::request,
184 };
185
186 use mithril_api_spec::APISpec;
187 use mithril_common::messages::{
188 CardanoDatabaseDigestListItemMessage, CardanoDatabaseSnapshotListItemMessage,
189 CardanoDatabaseSnapshotMessage,
190 };
191 use mithril_common::{MITHRIL_CLIENT_TYPE_HEADER, MITHRIL_ORIGIN_TAG_HEADER};
192 use mithril_persistence::sqlite::HydrationError;
193
194 use crate::{initialize_dependencies, services::MockMessageService};
195
196 use super::*;
197
198 fn setup_router(
199 state: RouterState,
200 ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
201 let cors = warp::cors()
202 .allow_any_origin()
203 .allow_headers(vec!["content-type"])
204 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
205
206 warp::any().and(routes(&state).with(cors))
207 }
208
209 #[tokio::test]
210 async fn test_cardano_database_get_ok() {
211 let mut mock_http_message_service = MockMessageService::new();
212 mock_http_message_service
213 .expect_get_cardano_database_list_message()
214 .return_once(|_| Ok(vec![CardanoDatabaseSnapshotListItemMessage::dummy()]))
215 .once();
216
217 let mut dependency_manager = initialize_dependencies!().await;
218
219 dependency_manager.message_service = Arc::new(mock_http_message_service);
220
221 let method = Method::GET.as_str();
222 let path = "/artifact/cardano-database";
223
224 let response = request()
225 .method(method)
226 .path(path)
227 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
228 dependency_manager,
229 ))))
230 .await;
231
232 APISpec::verify_conformity(
233 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
234 method,
235 path,
236 "application/json",
237 &Null,
238 &response,
239 &StatusCode::OK,
240 )
241 .unwrap();
242 }
243
244 #[tokio::test]
245 async fn test_cardano_database_get_ko() {
246 let mut mock_http_message_service = MockMessageService::new();
247 mock_http_message_service
248 .expect_get_cardano_database_list_message()
249 .return_once(|_| Err(HydrationError::InvalidData("invalid data".to_string()).into()))
250 .once();
251 let mut dependency_manager = initialize_dependencies!().await;
252 dependency_manager.message_service = Arc::new(mock_http_message_service);
253
254 let method = Method::GET.as_str();
255 let path = "/artifact/cardano-database";
256
257 let response = request()
258 .method(method)
259 .path(path)
260 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
261 dependency_manager,
262 ))))
263 .await;
264
265 APISpec::verify_conformity(
266 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
267 method,
268 path,
269 "application/json",
270 &Null,
271 &response,
272 &StatusCode::INTERNAL_SERVER_ERROR,
273 )
274 .unwrap();
275 }
276
277 #[tokio::test]
278 async fn test_cardano_database_detail_increments_artifact_detail_total_served_since_startup_metric(
279 ) {
280 let method = Method::GET.as_str();
281 let path = "/artifact/cardano-database/{hash}";
282 let dependency_manager = Arc::new(initialize_dependencies!().await);
283 let initial_counter_value = dependency_manager
284 .metrics_service
285 .get_artifact_detail_cardano_database_total_served_since_startup()
286 .get(&["TEST", "CLI"]);
287
288 request()
289 .method(method)
290 .path(path)
291 .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
292 .header(MITHRIL_CLIENT_TYPE_HEADER, "CLI")
293 .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
294 dependency_manager.clone(),
295 &["TEST"],
296 )))
297 .await;
298
299 assert_eq!(
300 initial_counter_value + 1,
301 dependency_manager
302 .metrics_service
303 .get_artifact_detail_cardano_database_total_served_since_startup()
304 .get(&["TEST", "CLI"]),
305 );
306 }
307
308 #[tokio::test]
309 async fn test_cardano_database_detail_get_ok() {
310 let mut mock_http_message_service = MockMessageService::new();
311 mock_http_message_service
312 .expect_get_cardano_database_message()
313 .return_once(|_| Ok(Some(CardanoDatabaseSnapshotMessage::dummy())))
314 .once();
315 let mut dependency_manager = initialize_dependencies!().await;
316 dependency_manager.message_service = Arc::new(mock_http_message_service);
317
318 let method = Method::GET.as_str();
319 let path = "/artifact/cardano-database/{hash}";
320
321 let response = request()
322 .method(method)
323 .path(path)
324 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
325 dependency_manager,
326 ))))
327 .await;
328
329 APISpec::verify_conformity(
330 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
331 method,
332 path,
333 "application/json",
334 &Null,
335 &response,
336 &StatusCode::OK,
337 )
338 .unwrap();
339 }
340
341 #[tokio::test]
342 async fn test_cardano_database_detail_returns_404_not_found_when_no_cardano_database_snapshot()
343 {
344 let mut mock_http_message_service = MockMessageService::new();
345 mock_http_message_service
346 .expect_get_cardano_database_message()
347 .return_once(|_| Ok(None))
348 .once();
349 let mut dependency_manager = initialize_dependencies!().await;
350 dependency_manager.message_service = Arc::new(mock_http_message_service);
351
352 let method = Method::GET.as_str();
353 let path = "/artifact/cardano-database/{hash}";
354
355 let response = request()
356 .method(method)
357 .path(path)
358 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
359 dependency_manager,
360 ))))
361 .await;
362
363 APISpec::verify_conformity(
364 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
365 method,
366 path,
367 "application/json",
368 &Null,
369 &response,
370 &StatusCode::NOT_FOUND,
371 )
372 .unwrap();
373 }
374
375 #[tokio::test]
376 async fn test_cardano_database_detail_get_ko() {
377 let mut mock_http_message_service = MockMessageService::new();
378 mock_http_message_service
379 .expect_get_cardano_database_message()
380 .return_once(|_| Err(HydrationError::InvalidData("invalid data".to_string()).into()))
381 .once();
382 let mut dependency_manager = initialize_dependencies!().await;
383 dependency_manager.message_service = Arc::new(mock_http_message_service);
384
385 let method = Method::GET.as_str();
386 let path = "/artifact/cardano-database/{hash}";
387
388 let response = request()
389 .method(method)
390 .path(path)
391 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
392 dependency_manager,
393 ))))
394 .await;
395
396 APISpec::verify_conformity(
397 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
398 method,
399 path,
400 "application/json",
401 &Null,
402 &response,
403 &StatusCode::INTERNAL_SERVER_ERROR,
404 )
405 .unwrap();
406 }
407
408 #[tokio::test]
409 async fn test_cardano_database_get_digests_ok() {
410 let mut mock_http_message_service = MockMessageService::new();
411 mock_http_message_service
412 .expect_get_cardano_database_digest_list_message()
413 .return_once(|| Ok(vec![CardanoDatabaseDigestListItemMessage::dummy()]))
414 .once();
415 let mut dependency_manager = initialize_dependencies!().await;
416 dependency_manager.message_service = Arc::new(mock_http_message_service);
417
418 let method = Method::GET.as_str();
419 let path = "/artifact/cardano-database/digests";
420
421 let response = request()
422 .method(method)
423 .path(path)
424 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
425 dependency_manager,
426 ))))
427 .await;
428
429 APISpec::verify_conformity(
430 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
431 method,
432 path,
433 "application/json",
434 &Null,
435 &response,
436 &StatusCode::OK,
437 )
438 .unwrap();
439 }
440
441 #[tokio::test]
442 async fn test_cardano_database_get_digests_ko() {
443 let mut mock_http_message_service = MockMessageService::new();
444 mock_http_message_service
445 .expect_get_cardano_database_digest_list_message()
446 .return_once(|| Err(HydrationError::InvalidData("invalid data".to_string()).into()))
447 .once();
448 let mut dependency_manager = initialize_dependencies!().await;
449 dependency_manager.message_service = Arc::new(mock_http_message_service);
450
451 let method = Method::GET.as_str();
452 let path = "/artifact/cardano-database/digests";
453
454 let response = request()
455 .method(method)
456 .path(path)
457 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
458 dependency_manager,
459 ))))
460 .await;
461
462 APISpec::verify_conformity(
463 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
464 method,
465 path,
466 "application/json",
467 &Null,
468 &response,
469 &StatusCode::INTERNAL_SERVER_ERROR,
470 )
471 .unwrap();
472 }
473}