mithril_aggregator/http_server/routes/artifact_routes/
cardano_stake_distribution.rs

1use 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
13/// GET /artifact/cardano-stake-distributions
14fn 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
24/// GET /artifact/cardano-stake-distribution/:id
25fn 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
36/// GET /artifact/cardano-stake-distribution/epoch/:epoch
37fn 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    /// List CardanoStakeDistribution artifacts
62    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    /// Get Artifact by signed entity id
79    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    /// Get Artifact by epoch
106    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}