mithril_aggregator/http_server/routes/artifact_routes/
cardano_stake_distribution.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_stake_distributions(router_state)
9 .or(artifact_cardano_stake_distribution_by_id(router_state))
10 .or(artifact_cardano_stake_distribution_by_epoch(router_state))
11}
12
13fn artifact_cardano_stake_distributions(
15 router_state: &RouterState,
16) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
17 warp::path!("artifact" / "cardano-stake-distributions")
18 .and(warp::get())
19 .and(middlewares::with_logger(router_state))
20 .and(middlewares::with_http_message_service(router_state))
21 .and_then(handlers::list_artifacts)
22}
23
24fn artifact_cardano_stake_distribution_by_id(
26 router_state: &RouterState,
27) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
28 warp::path!("artifact" / "cardano-stake-distribution" / String)
29 .and(warp::get())
30 .and(middlewares::with_logger(router_state))
31 .and(middlewares::with_http_message_service(router_state))
32 .and(middlewares::with_metrics_service(router_state))
33 .and_then(handlers::get_artifact_by_signed_entity_id)
34}
35
36fn artifact_cardano_stake_distribution_by_epoch(
38 router_state: &RouterState,
39) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
40 warp::path!("artifact" / "cardano-stake-distribution" / "epoch" / String)
41 .and(warp::get())
42 .and(middlewares::with_logger(router_state))
43 .and(middlewares::with_http_message_service(router_state))
44 .and(middlewares::with_metrics_service(router_state))
45 .and_then(handlers::get_artifact_by_epoch)
46}
47
48pub mod handlers {
49 use crate::http_server::routes::reply;
50 use crate::services::MessageService;
51 use crate::MetricsService;
52
53 use mithril_common::entities::Epoch;
54 use slog::{warn, Logger};
55 use std::convert::Infallible;
56 use std::sync::Arc;
57 use warp::http::StatusCode;
58
59 pub const LIST_MAX_ITEMS: usize = 20;
60
61 pub async fn list_artifacts(
63 logger: Logger,
64 http_message_service: Arc<dyn MessageService>,
65 ) -> Result<impl warp::Reply, Infallible> {
66 match http_message_service
67 .get_cardano_stake_distribution_list_message(LIST_MAX_ITEMS)
68 .await
69 {
70 Ok(message) => Ok(reply::json(&message, StatusCode::OK)),
71 Err(err) => {
72 warn!(logger, "get_cardano_stake_distribution_list::error"; "error" => ?err);
73 Ok(reply::server_error(err))
74 }
75 }
76 }
77
78 pub async fn get_artifact_by_signed_entity_id(
80 signed_entity_id: String,
81 logger: Logger,
82 http_message_service: Arc<dyn MessageService>,
83 metrics_service: Arc<MetricsService>,
84 ) -> Result<impl warp::Reply, Infallible> {
85 metrics_service
86 .get_artifact_detail_cardano_stake_distribution_total_served_since_startup()
87 .increment();
88
89 match http_message_service
90 .get_cardano_stake_distribution_message(&signed_entity_id)
91 .await
92 {
93 Ok(Some(message)) => Ok(reply::json(&message, StatusCode::OK)),
94 Ok(None) => {
95 warn!(logger, "get_cardano_stake_distribution_details::not_found");
96 Ok(reply::empty(StatusCode::NOT_FOUND))
97 }
98 Err(err) => {
99 warn!(logger, "get_cardano_stake_distribution_details::error"; "error" => ?err);
100 Ok(reply::server_error(err))
101 }
102 }
103 }
104
105 pub async fn get_artifact_by_epoch(
107 epoch: String,
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_artifact_detail_cardano_stake_distribution_total_served_since_startup()
114 .increment();
115
116 let artifact_epoch = match epoch.parse::<u64>() {
117 Ok(epoch) => Epoch(epoch),
118 Err(err) => {
119 warn!(logger, "get_artifact_by_epoch::invalid_epoch"; "error" => ?err);
120 return Ok(reply::bad_request(
121 "invalid_epoch".to_string(),
122 err.to_string(),
123 ));
124 }
125 };
126
127 match http_message_service
128 .get_cardano_stake_distribution_message_by_epoch(artifact_epoch)
129 .await
130 {
131 Ok(Some(message)) => Ok(reply::json(&message, StatusCode::OK)),
132 Ok(None) => {
133 warn!(
134 logger,
135 "get_cardano_stake_distribution_details_by_epoch::not_found"
136 );
137 Ok(reply::empty(StatusCode::NOT_FOUND))
138 }
139 Err(err) => {
140 warn!(logger, "get_cardano_stake_distribution_details_by_epoch::error"; "error" => ?err);
141 Ok(reply::server_error(err))
142 }
143 }
144 }
145}
146
147#[cfg(test)]
148pub mod tests {
149 use anyhow::anyhow;
150 use serde_json::Value::Null;
151 use std::sync::Arc;
152 use warp::{
153 http::{Method, StatusCode},
154 test::request,
155 };
156
157 use mithril_common::{
158 messages::{CardanoStakeDistributionListItemMessage, CardanoStakeDistributionMessage},
159 test_utils::apispec::APISpec,
160 };
161
162 use crate::{initialize_dependencies, services::MockMessageService};
163
164 use super::*;
165
166 fn setup_router(
167 state: RouterState,
168 ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
169 let cors = warp::cors()
170 .allow_any_origin()
171 .allow_headers(vec!["content-type"])
172 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
173
174 warp::any().and(routes(&state).with(cors))
175 }
176
177 #[tokio::test]
178 async fn test_cardano_stake_distributions_returns_ok() {
179 let message = vec![CardanoStakeDistributionListItemMessage::dummy()];
180 let mut mock_http_message_service = MockMessageService::new();
181 mock_http_message_service
182 .expect_get_cardano_stake_distribution_list_message()
183 .return_once(|_| Ok(message))
184 .once();
185 let mut dependency_manager = initialize_dependencies!().await;
186 dependency_manager.message_service = Arc::new(mock_http_message_service);
187
188 let method = Method::GET.as_str();
189 let path = "/artifact/cardano-stake-distributions";
190
191 let response = request()
192 .method(method)
193 .path(path)
194 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
195 dependency_manager,
196 ))))
197 .await;
198
199 APISpec::verify_conformity(
200 APISpec::get_all_spec_files(),
201 method,
202 path,
203 "application/json",
204 &Null,
205 &response,
206 &StatusCode::OK,
207 )
208 .unwrap();
209 }
210
211 #[tokio::test]
212 async fn test_cardano_stake_distributions_returns_ko_500_when_error() {
213 let mut mock_http_message_service = MockMessageService::new();
214 mock_http_message_service
215 .expect_get_cardano_stake_distribution_list_message()
216 .return_once(|_| Err(anyhow!("an error occured")))
217 .once();
218 let mut dependency_manager = initialize_dependencies!().await;
219 dependency_manager.message_service = Arc::new(mock_http_message_service);
220
221 let method = Method::GET.as_str();
222 let path = "/artifact/cardano-stake-distributions";
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_all_spec_files(),
234 method,
235 path,
236 "application/json",
237 &Null,
238 &response,
239 &StatusCode::INTERNAL_SERVER_ERROR,
240 )
241 .unwrap();
242 }
243
244 #[tokio::test]
245 async fn test_cardano_stake_distribution_increments_artifact_detail_total_served_since_startup_metric(
246 ) {
247 let method = Method::GET.as_str();
248 let dependency_manager = Arc::new(initialize_dependencies!().await);
249 let initial_counter_value = dependency_manager
250 .metrics_service
251 .get_artifact_detail_cardano_stake_distribution_total_served_since_startup()
252 .get();
253 {
254 let path = "/artifact/cardano-stake-distribution/{hash}";
255
256 request()
257 .method(method)
258 .path(path)
259 .reply(&setup_router(RouterState::new_with_dummy_config(
260 dependency_manager.clone(),
261 )))
262 .await;
263
264 assert_eq!(
265 initial_counter_value + 1,
266 dependency_manager
267 .metrics_service
268 .get_artifact_detail_cardano_stake_distribution_total_served_since_startup()
269 .get()
270 );
271 }
272
273 {
274 let base_path = "/artifact/cardano-stake-distribution/epoch";
275
276 request()
277 .method(method)
278 .path(&format!("{base_path}/123"))
279 .reply(&setup_router(RouterState::new_with_dummy_config(
280 dependency_manager.clone(),
281 )))
282 .await;
283
284 assert_eq!(
285 initial_counter_value + 2,
286 dependency_manager
287 .metrics_service
288 .get_artifact_detail_cardano_stake_distribution_total_served_since_startup()
289 .get()
290 );
291 }
292 }
293
294 #[tokio::test]
295 async fn test_cardano_stake_distribution_returns_ok() {
296 let message = CardanoStakeDistributionMessage::dummy();
297 let mut mock_http_message_service = MockMessageService::new();
298 mock_http_message_service
299 .expect_get_cardano_stake_distribution_message()
300 .return_once(|_| Ok(Some(message)))
301 .once();
302 let mut dependency_manager = initialize_dependencies!().await;
303 dependency_manager.message_service = Arc::new(mock_http_message_service);
304
305 let method = Method::GET.as_str();
306 let path = "/artifact/cardano-stake-distribution/{hash}";
307
308 let response = request()
309 .method(method)
310 .path(path)
311 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
312 dependency_manager,
313 ))))
314 .await;
315
316 APISpec::verify_conformity(
317 APISpec::get_all_spec_files(),
318 method,
319 path,
320 "application/json",
321 &Null,
322 &response,
323 &StatusCode::OK,
324 )
325 .unwrap();
326 }
327
328 #[tokio::test]
329 async fn test_cardano_stake_distribution_returns_404_not_found_when_no_record() {
330 let mut mock_http_message_service = MockMessageService::new();
331 mock_http_message_service
332 .expect_get_cardano_stake_distribution_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-stake-distribution/{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_stake_distribution_returns_ko_500_when_error() {
363 let mut mock_http_message_service = MockMessageService::new();
364 mock_http_message_service
365 .expect_get_cardano_stake_distribution_message()
366 .return_once(|_| Err(anyhow!("an error occured")))
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-stake-distribution/{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_stake_distribution_by_epoch_returns_ok() {
396 let message = CardanoStakeDistributionMessage::dummy();
397 let mut mock_http_message_service = MockMessageService::new();
398 mock_http_message_service
399 .expect_get_cardano_stake_distribution_message_by_epoch()
400 .return_once(|_| Ok(Some(message)))
401 .once();
402 let mut dependency_manager = initialize_dependencies!().await;
403 dependency_manager.message_service = Arc::new(mock_http_message_service);
404
405 let method = Method::GET.as_str();
406 let base_path = "/artifact/cardano-stake-distribution/epoch";
407
408 let response = request()
409 .method(method)
410 .path(&format!("{base_path}/123"))
411 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
412 dependency_manager,
413 ))))
414 .await;
415
416 APISpec::verify_conformity(
417 APISpec::get_all_spec_files(),
418 method,
419 &format!("{base_path}/{{epoch}}"),
420 "application/json",
421 &Null,
422 &response,
423 &StatusCode::OK,
424 )
425 .unwrap();
426 }
427
428 #[tokio::test]
429 async fn test_cardano_stake_distribution_by_epoch_returns_400_bad_request_when_invalid_epoch() {
430 let mock_http_message_service = MockMessageService::new();
431 let mut dependency_manager = initialize_dependencies!().await;
432 dependency_manager.message_service = Arc::new(mock_http_message_service);
433
434 let method = Method::GET.as_str();
435 let base_path = "/artifact/cardano-stake-distribution/epoch";
436
437 let response = request()
438 .method(method)
439 .path(&format!("{base_path}/invalid-epoch"))
440 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
441 dependency_manager,
442 ))))
443 .await;
444
445 APISpec::verify_conformity(
446 APISpec::get_all_spec_files(),
447 method,
448 &format!("{base_path}/{{epoch}}"),
449 "application/json",
450 &Null,
451 &response,
452 &StatusCode::BAD_REQUEST,
453 )
454 .unwrap();
455 }
456
457 #[tokio::test]
458 async fn test_cardano_stake_distribution_by_epoch_returns_404_not_found_when_no_record() {
459 let mut mock_http_message_service = MockMessageService::new();
460 mock_http_message_service
461 .expect_get_cardano_stake_distribution_message_by_epoch()
462 .return_once(|_| Ok(None))
463 .once();
464 let mut dependency_manager = initialize_dependencies!().await;
465 dependency_manager.message_service = Arc::new(mock_http_message_service);
466
467 let method = Method::GET.as_str();
468 let base_path = "/artifact/cardano-stake-distribution/epoch";
469
470 let response = request()
471 .method(method)
472 .path(&format!("{base_path}/123"))
473 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
474 dependency_manager,
475 ))))
476 .await;
477
478 APISpec::verify_conformity(
479 APISpec::get_all_spec_files(),
480 method,
481 &format!("{base_path}/{{epoch}}"),
482 "application/json",
483 &Null,
484 &response,
485 &StatusCode::NOT_FOUND,
486 )
487 .unwrap();
488 }
489
490 #[tokio::test]
491 async fn test_cardano_stake_distribution_by_epoch_returns_ko_500_when_error() {
492 let mut mock_http_message_service = MockMessageService::new();
493 mock_http_message_service
494 .expect_get_cardano_stake_distribution_message_by_epoch()
495 .return_once(|_| Err(anyhow!("an error occured")))
496 .once();
497 let mut dependency_manager = initialize_dependencies!().await;
498 dependency_manager.message_service = Arc::new(mock_http_message_service);
499
500 let method = Method::GET.as_str();
501 let base_path = "/artifact/cardano-stake-distribution/epoch";
502
503 let response = request()
504 .method(method)
505 .path(&format!("{base_path}/123"))
506 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
507 dependency_manager,
508 ))))
509 .await;
510
511 APISpec::verify_conformity(
512 APISpec::get_all_spec_files(),
513 method,
514 &format!("{base_path}/{{epoch}}"),
515 "application/json",
516 &Null,
517 &response,
518 &StatusCode::INTERNAL_SERVER_ERROR,
519 )
520 .unwrap();
521 }
522}