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