mithril_aggregator_client/query/get/
get_cardano_stake_distribution.rs

1use async_trait::async_trait;
2use reqwest::StatusCode;
3use std::fmt::{Display, Formatter};
4
5use mithril_common::entities::EpochSpecifier;
6use mithril_common::messages::CardanoStakeDistributionMessage;
7
8use crate::AggregatorHttpClientResult;
9use crate::query::{AggregatorQuery, QueryContext, QueryMethod, ResponseExt};
10
11/// Query to get a Cardano stake distribution
12pub struct GetCardanoStakeDistributionQuery {
13    scope: QueryScope,
14}
15
16enum QueryScope {
17    /// Fetch the Cardano stake distribution with the given hash
18    Hash(String),
19    /// Fetch the Cardano stake distribution for a specified epoch
20    Epoch(EpochSpecifier),
21}
22
23impl Display for QueryScope {
24    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25        match &self {
26            QueryScope::Hash(hash) => write!(f, "hash({})", hash),
27            QueryScope::Epoch(specifier) => {
28                write!(f, "epoch({specifier})")
29            }
30        }
31    }
32}
33
34impl GetCardanoStakeDistributionQuery {
35    /// Instantiate a query to get a Cardano stake distribution by hash
36    pub fn by_hash<H: Into<String>>(hash: H) -> Self {
37        Self {
38            scope: QueryScope::Hash(hash.into()),
39        }
40    }
41
42    /// Instantiate a query to get a Cardano stake distribution for a specified epoch
43    pub fn for_epoch(epoch_specifier: EpochSpecifier) -> Self {
44        Self {
45            scope: QueryScope::Epoch(epoch_specifier),
46        }
47    }
48}
49
50#[cfg_attr(target_family = "wasm", async_trait(?Send))]
51#[cfg_attr(not(target_family = "wasm"), async_trait)]
52impl AggregatorQuery for GetCardanoStakeDistributionQuery {
53    type Response = Option<CardanoStakeDistributionMessage>;
54    type Body = ();
55
56    fn method() -> QueryMethod {
57        QueryMethod::Get
58    }
59
60    fn route(&self) -> String {
61        match &self.scope {
62            QueryScope::Hash(hash) => format!("artifact/cardano-stake-distribution/{hash}"),
63            QueryScope::Epoch(specifier) => {
64                format!("artifact/cardano-stake-distribution/epoch/{specifier}")
65            }
66        }
67    }
68
69    async fn handle_response(
70        &self,
71        context: QueryContext,
72    ) -> AggregatorHttpClientResult<Self::Response> {
73        match context.response.status() {
74            StatusCode::OK => context.response.parse_json_option().await,
75            StatusCode::NOT_FOUND => Ok(None),
76            _ => Err(context.unhandled_status_code().await),
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use serde_json::json;
84
85    use mithril_common::entities::Epoch;
86    use mithril_common::test::double::Dummy;
87
88    use crate::AggregatorHttpClientError;
89    use crate::test::{assert_error_matches, setup_server_and_client};
90
91    use super::*;
92
93    #[tokio::test]
94    async fn test_cardano_stake_distribution_by_hash_ok_200() {
95        let (server, client) = setup_server_and_client();
96        let expected_message = CardanoStakeDistributionMessage::dummy();
97        let _server_mock = server.mock(|when, then| {
98            when.path(format!(
99                "/artifact/cardano-stake-distribution/{}",
100                expected_message.hash
101            ));
102            then.status(200).body(json!(expected_message).to_string());
103        });
104
105        let fetched_message = client
106            .send(GetCardanoStakeDistributionQuery::by_hash(
107                &expected_message.hash,
108            ))
109            .await
110            .unwrap();
111
112        assert_eq!(Some(expected_message), fetched_message);
113    }
114
115    #[tokio::test]
116    async fn test_cardano_stake_distribution_by_epoch_ok_200() {
117        let (server, client) = setup_server_and_client();
118        let expected_message = CardanoStakeDistributionMessage::dummy();
119        let _server_mock = server.mock(|when, then| {
120            when.path("/artifact/cardano-stake-distribution/epoch/6");
121            then.status(200).body(json!(expected_message).to_string());
122        });
123
124        let fetched_message = client
125            .send(GetCardanoStakeDistributionQuery::for_epoch(
126                EpochSpecifier::Number(Epoch(6)),
127            ))
128            .await
129            .unwrap();
130
131        assert_eq!(Some(expected_message), fetched_message);
132    }
133
134    #[tokio::test]
135    async fn test_cardano_stake_distribution_for_latest_epoch_ok_200() {
136        let (server, client) = setup_server_and_client();
137        let expected_message = CardanoStakeDistributionMessage::dummy();
138        let _server_mock = server.mock(|when, then| {
139            when.path("/artifact/cardano-stake-distribution/epoch/latest");
140            then.status(200).body(json!(expected_message).to_string());
141        });
142
143        let fetched_message = client
144            .send(GetCardanoStakeDistributionQuery::for_epoch(
145                EpochSpecifier::Latest,
146            ))
147            .await
148            .unwrap();
149
150        assert_eq!(Some(expected_message), fetched_message);
151    }
152
153    #[tokio::test]
154    async fn test_cardano_stake_distribution_for_latest_epoch_with_offset_ok_200() {
155        let (server, client) = setup_server_and_client();
156        let expected_message = CardanoStakeDistributionMessage::dummy();
157        let _server_mock = server.mock(|when, then| {
158            when.path("/artifact/cardano-stake-distribution/epoch/latest-5");
159            then.status(200).body(json!(expected_message).to_string());
160        });
161
162        let fetched_message = client
163            .send(GetCardanoStakeDistributionQuery::for_epoch(
164                EpochSpecifier::LatestMinusOffset(5),
165            ))
166            .await
167            .unwrap();
168
169        assert_eq!(Some(expected_message), fetched_message);
170    }
171
172    #[tokio::test]
173    async fn test_cardano_stake_distribution_details_ok_404() {
174        let (server, client) = setup_server_and_client();
175        let _server_mock = server.mock(|when, then| {
176            when.any_request();
177            then.status(404);
178        });
179
180        let fetched_message = client
181            .send(GetCardanoStakeDistributionQuery::by_hash("whatever"))
182            .await
183            .unwrap();
184
185        assert_eq!(None, fetched_message);
186    }
187
188    #[tokio::test]
189    async fn test_cardano_stake_distribution_details_ko_500() {
190        let (server, client) = setup_server_and_client();
191        let _server_mock = server.mock(|when, then| {
192            when.any_request();
193            then.status(500).body("an error occurred");
194        });
195
196        let error = client
197            .send(GetCardanoStakeDistributionQuery::by_hash("whatever"))
198            .await
199            .unwrap_err();
200
201        assert_error_matches!(error, AggregatorHttpClientError::RemoteServerTechnical(_));
202    }
203}