mithril_client/certificate_client/
fetch.rs

1use anyhow::{anyhow, Context};
2use async_trait::async_trait;
3use slog::{crit, Logger};
4use std::sync::Arc;
5
6use mithril_common::certificate_chain::{CertificateRetriever, CertificateRetrieverError};
7use mithril_common::entities::Certificate;
8use mithril_common::messages::CertificateMessage;
9
10use crate::aggregator_client::{AggregatorClient, AggregatorClientError, AggregatorRequest};
11use crate::certificate_client::CertificateClient;
12use crate::{MithrilCertificate, MithrilCertificateListItem, MithrilResult};
13
14#[inline]
15pub(super) async fn list(
16    client: &CertificateClient,
17) -> MithrilResult<Vec<MithrilCertificateListItem>> {
18    let response = client
19        .aggregator_client
20        .get_content(AggregatorRequest::ListCertificates)
21        .await
22        .with_context(|| "CertificateClient can not get the certificate list")?;
23    let items = serde_json::from_str::<Vec<MithrilCertificateListItem>>(&response)
24        .with_context(|| "CertificateClient can not deserialize certificate list")?;
25
26    Ok(items)
27}
28
29#[inline]
30pub(super) async fn get(
31    client: &CertificateClient,
32    certificate_hash: &str,
33) -> MithrilResult<Option<MithrilCertificate>> {
34    client.retriever.get(certificate_hash).await
35}
36
37/// Internal type to implement the [InternalCertificateRetriever] trait and avoid a circular
38/// dependency between the [CertificateClient] and the [CommonMithrilCertificateVerifier] that need
39/// a [CertificateRetriever] as a dependency.
40pub(super) struct InternalCertificateRetriever {
41    aggregator_client: Arc<dyn AggregatorClient>,
42    logger: Logger,
43}
44
45impl InternalCertificateRetriever {
46    pub(super) fn new(
47        aggregator_client: Arc<dyn AggregatorClient>,
48        logger: Logger,
49    ) -> InternalCertificateRetriever {
50        InternalCertificateRetriever {
51            aggregator_client,
52            logger,
53        }
54    }
55
56    pub(super) async fn get(
57        &self,
58        certificate_hash: &str,
59    ) -> MithrilResult<Option<MithrilCertificate>> {
60        let response = self
61            .aggregator_client
62            .get_content(AggregatorRequest::GetCertificate {
63                hash: certificate_hash.to_string(),
64            })
65            .await;
66
67        match response {
68            Err(AggregatorClientError::RemoteServerLogical(_)) => Ok(None),
69            Err(e) => Err(e.into()),
70            Ok(response) => {
71                let message =
72                    serde_json::from_str::<CertificateMessage>(&response).inspect_err(|e| {
73                        crit!(
74                            self.logger, "Could not create certificate from API message";
75                            "error" => e.to_string(),
76                            "raw_message" => response
77                        );
78                    })?;
79
80                Ok(Some(message))
81            }
82        }
83    }
84}
85
86#[cfg_attr(target_family = "wasm", async_trait(?Send))]
87#[cfg_attr(not(target_family = "wasm"), async_trait)]
88impl CertificateRetriever for InternalCertificateRetriever {
89    async fn get_certificate_details(
90        &self,
91        certificate_hash: &str,
92    ) -> Result<Certificate, CertificateRetrieverError> {
93        self.get(certificate_hash)
94            .await
95            .map_err(CertificateRetrieverError)?
96            .map(|message| message.try_into())
97            .transpose()
98            .map_err(CertificateRetrieverError)?
99            .ok_or(CertificateRetrieverError(anyhow!(format!(
100                "Certificate does not exist: '{}'",
101                certificate_hash
102            ))))
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use mithril_common::test_utils::fake_data;
109
110    use crate::certificate_client::tests_utils::CertificateClientTestBuilder;
111
112    use super::*;
113
114    #[tokio::test]
115    async fn get_certificate_list() {
116        let expected = vec![
117            MithrilCertificateListItem {
118                hash: "cert-hash-123".to_string(),
119                ..MithrilCertificateListItem::dummy()
120            },
121            MithrilCertificateListItem {
122                hash: "cert-hash-456".to_string(),
123                ..MithrilCertificateListItem::dummy()
124            },
125        ];
126        let message = expected.clone();
127        let certificate_client = CertificateClientTestBuilder::default()
128            .config_aggregator_client_mock(|mock| {
129                mock.expect_get_content()
130                    .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
131            })
132            .build();
133        let items = certificate_client.list().await.unwrap();
134
135        assert_eq!(expected, items);
136    }
137
138    #[tokio::test]
139    async fn get_certificate_empty_list() {
140        let certificate_client = CertificateClientTestBuilder::default()
141            .config_aggregator_client_mock(|mock| {
142                mock.expect_get_content().return_once(move |_| {
143                    Ok(serde_json::to_string::<Vec<MithrilCertificateListItem>>(&vec![]).unwrap())
144                });
145            })
146            .build();
147        let items = certificate_client.list().await.unwrap();
148
149        assert!(items.is_empty());
150    }
151
152    #[tokio::test]
153    async fn test_show_ok_some() {
154        let certificate_hash = "cert-hash-123".to_string();
155        let certificate = fake_data::certificate(certificate_hash.clone());
156        let expected_certificate = certificate.clone();
157
158        let certificate_client = CertificateClientTestBuilder::default()
159            .config_aggregator_client_mock(|mock| {
160                mock.expect_get_content()
161                    .return_once(move |_| {
162                        let message: CertificateMessage = certificate.try_into().unwrap();
163                        Ok(serde_json::to_string(&message).unwrap())
164                    })
165                    .times(1);
166            })
167            .build();
168
169        let cert = certificate_client
170            .get("cert-hash-123")
171            .await
172            .unwrap()
173            .expect("The certificate should be found")
174            .try_into()
175            .unwrap();
176
177        assert_eq!(expected_certificate, cert);
178    }
179
180    #[tokio::test]
181    async fn test_show_ok_none() {
182        let certificate_client = CertificateClientTestBuilder::default()
183            .config_aggregator_client_mock(|mock| {
184                mock.expect_get_content()
185                    .return_once(move |_| {
186                        Err(AggregatorClientError::RemoteServerLogical(anyhow!(
187                            "an error"
188                        )))
189                    })
190                    .times(1);
191            })
192            .build();
193
194        assert!(certificate_client
195            .get("cert-hash-123")
196            .await
197            .unwrap()
198            .is_none());
199    }
200
201    #[tokio::test]
202    async fn test_show_ko() {
203        let certificate_client = CertificateClientTestBuilder::default()
204            .config_aggregator_client_mock(|mock| {
205                mock.expect_get_content()
206                    .return_once(move |_| {
207                        Err(AggregatorClientError::RemoteServerTechnical(anyhow!(
208                            "an error"
209                        )))
210                    })
211                    .times(1);
212            })
213            .build();
214
215        certificate_client
216            .get("cert-hash-123")
217            .await
218            .expect_err("The certificate client should fail here.");
219    }
220}