1use std::collections::HashMap;
2#[cfg(not(target_family = "wasm"))]
3use std::str::FromStr;
4use std::sync::Arc;
5
6use anyhow::{Context, anyhow};
7#[cfg(any(feature = "fs", not(target_family = "wasm")))]
8use chrono::Utc;
9#[cfg(not(target_family = "wasm"))]
10use rand::SeedableRng;
11#[cfg(not(target_family = "wasm"))]
12use rand::rngs::StdRng;
13use reqwest::Url;
14use serde::{Deserialize, Serialize};
15use slog::{Logger, o};
16
17#[cfg(not(target_family = "wasm"))]
18use mithril_aggregator_discovery::{
19 AggregatorDiscoverer, AggregatorEndpoint, CapableAggregatorDiscoverer,
20 HttpConfigAggregatorDiscoverer, RequiredAggregatorCapabilities, ShuffleAggregatorDiscoverer,
21};
22use mithril_common::api_version::APIVersionProvider;
23use mithril_common::{MITHRIL_CLIENT_TYPE_HEADER, MITHRIL_ORIGIN_TAG_HEADER};
24
25use crate::MithrilResult;
26use crate::aggregator_client::{AggregatorClient, AggregatorHTTPClient};
27use crate::cardano_database_client::CardanoDatabaseClient;
28use crate::cardano_stake_distribution_client::CardanoStakeDistributionClient;
29use crate::cardano_transaction_client::CardanoTransactionClient;
30#[cfg(feature = "unstable")]
31use crate::certificate_client::CertificateVerifierCache;
32use crate::certificate_client::{
33 CertificateClient, CertificateVerifier, MithrilCertificateVerifier,
34};
35#[cfg(not(target_family = "wasm"))]
36use crate::common::MithrilNetwork;
37use crate::era::{AggregatorHttpEraFetcher, EraFetcher, MithrilEraClient};
38use crate::feedback::{FeedbackReceiver, FeedbackSender};
39#[cfg(feature = "fs")]
40use crate::file_downloader::{
41 FileDownloadRetryPolicy, FileDownloader, HttpFileDownloader, RetryDownloader,
42};
43use crate::mithril_stake_distribution_client::MithrilStakeDistributionClient;
44use crate::snapshot_client::SnapshotClient;
45#[cfg(feature = "fs")]
46use crate::utils::AncillaryVerifier;
47#[cfg(feature = "fs")]
48use crate::utils::TimestampTempDirectoryProvider;
49
50const DEFAULT_CLIENT_TYPE: &str = "LIBRARY";
51
52#[cfg(target_family = "wasm")]
53const fn one_week_in_seconds() -> u32 {
54 604800
55}
56
57pub enum AggregatorDiscoveryType {
59 Url(String),
61 #[cfg(not(target_family = "wasm"))]
63 Automatic(MithrilNetwork),
64}
65
66#[cfg(not(target_family = "wasm"))]
67impl FromStr for AggregatorDiscoveryType {
68 type Err = anyhow::Error;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 if let Some(network) = s.strip_prefix("auto:") {
72 Ok(AggregatorDiscoveryType::Automatic(MithrilNetwork::new(
73 network.to_string(),
74 )))
75 } else {
76 Ok(AggregatorDiscoveryType::Url(s.to_string()))
77 }
78 }
79}
80
81pub enum GenesisVerificationKey {
83 JsonHex(String),
85}
86
87#[derive(Debug, Clone, Default, Serialize, Deserialize)]
89pub struct ClientOptions {
90 pub http_headers: Option<HashMap<String, String>>,
92
93 #[cfg(target_family = "wasm")]
95 #[cfg_attr(target_family = "wasm", serde(default))]
96 pub origin_tag: Option<String>,
97
98 #[cfg(target_family = "wasm")]
100 #[cfg_attr(target_family = "wasm", serde(default))]
101 pub unstable: bool,
102
103 #[cfg(target_family = "wasm")]
109 #[cfg_attr(target_family = "wasm", serde(default))]
110 pub enable_certificate_chain_verification_cache: bool,
111
112 #[cfg(target_family = "wasm")]
119 #[cfg_attr(target_family = "wasm", serde(default = "one_week_in_seconds"))]
120 pub certificate_chain_verification_cache_duration_in_seconds: u32,
121}
122
123impl ClientOptions {
124 pub fn new(http_headers: Option<HashMap<String, String>>) -> Self {
126 Self {
127 http_headers,
128 #[cfg(target_family = "wasm")]
129 origin_tag: None,
130 #[cfg(target_family = "wasm")]
131 unstable: false,
132 #[cfg(target_family = "wasm")]
133 enable_certificate_chain_verification_cache: false,
134 #[cfg(target_family = "wasm")]
135 certificate_chain_verification_cache_duration_in_seconds: one_week_in_seconds(),
136 }
137 }
138
139 #[cfg(target_family = "wasm")]
141 pub fn with_unstable_features(self, unstable: bool) -> Self {
142 Self { unstable, ..self }
143 }
144}
145
146#[derive(Clone)]
150pub struct Client {
151 certificate_client: Arc<CertificateClient>,
152 mithril_stake_distribution_client: Arc<MithrilStakeDistributionClient>,
153 snapshot_client: Arc<SnapshotClient>,
154 cardano_database_client: Arc<CardanoDatabaseClient>,
155 cardano_transaction_client: Arc<CardanoTransactionClient>,
156 cardano_stake_distribution_client: Arc<CardanoStakeDistributionClient>,
157 mithril_era_client: Arc<MithrilEraClient>,
158}
159
160impl Client {
161 pub fn certificate(&self) -> Arc<CertificateClient> {
163 self.certificate_client.clone()
164 }
165
166 pub fn mithril_stake_distribution(&self) -> Arc<MithrilStakeDistributionClient> {
168 self.mithril_stake_distribution_client.clone()
169 }
170
171 #[deprecated(since = "0.12.35", note = "superseded by `cardano_database_v2`")]
173 pub fn cardano_database(&self) -> Arc<SnapshotClient> {
174 self.snapshot_client.clone()
175 }
176
177 pub fn cardano_database_v2(&self) -> Arc<CardanoDatabaseClient> {
179 self.cardano_database_client.clone()
180 }
181
182 pub fn cardano_transaction(&self) -> Arc<CardanoTransactionClient> {
184 self.cardano_transaction_client.clone()
185 }
186
187 pub fn cardano_stake_distribution(&self) -> Arc<CardanoStakeDistributionClient> {
189 self.cardano_stake_distribution_client.clone()
190 }
191
192 pub fn mithril_era_client(&self) -> Arc<MithrilEraClient> {
194 self.mithril_era_client.clone()
195 }
196}
197
198pub struct ClientBuilder {
200 aggregator_discovery: AggregatorDiscoveryType,
201 #[cfg(not(target_family = "wasm"))]
202 aggregator_capabilities: Option<RequiredAggregatorCapabilities>,
203 #[cfg(not(target_family = "wasm"))]
204 aggregator_discoverer: Option<Arc<dyn AggregatorDiscoverer>>,
205 genesis_verification_key: Option<GenesisVerificationKey>,
206 origin_tag: Option<String>,
207 client_type: Option<String>,
208 #[cfg(feature = "fs")]
209 ancillary_verification_key: Option<String>,
210 aggregator_client: Option<Arc<dyn AggregatorClient>>,
211 certificate_verifier: Option<Arc<dyn CertificateVerifier>>,
212 #[cfg(feature = "fs")]
213 http_file_downloader: Option<Arc<dyn FileDownloader>>,
214 #[cfg(feature = "unstable")]
215 certificate_verifier_cache: Option<Arc<dyn CertificateVerifierCache>>,
216 era_fetcher: Option<Arc<dyn EraFetcher>>,
217 logger: Option<Logger>,
218 feedback_receivers: Vec<Arc<dyn FeedbackReceiver>>,
219 options: ClientOptions,
220}
221
222impl ClientBuilder {
223 #[deprecated(
226 since = "0.12.36",
227 note = "Use `new` method instead and set the genesis verification key with `set_genesis_verification_key`"
228 )]
229 pub fn aggregator(endpoint: &str, genesis_verification_key: &str) -> ClientBuilder {
230 Self::new(AggregatorDiscoveryType::Url(endpoint.to_string())).set_genesis_verification_key(
231 GenesisVerificationKey::JsonHex(genesis_verification_key.to_string()),
232 )
233 }
234
235 #[cfg(not(target_family = "wasm"))]
238 pub fn automatic(network: &str, genesis_verification_key: &str) -> ClientBuilder {
239 Self::new(AggregatorDiscoveryType::Automatic(MithrilNetwork::new(
240 network.to_string(),
241 )))
242 .set_genesis_verification_key(GenesisVerificationKey::JsonHex(
243 genesis_verification_key.to_string(),
244 ))
245 }
246
247 pub fn new(aggregator_discovery: AggregatorDiscoveryType) -> ClientBuilder {
249 Self {
250 aggregator_discovery,
251 #[cfg(not(target_family = "wasm"))]
252 aggregator_capabilities: None,
253 #[cfg(not(target_family = "wasm"))]
254 aggregator_discoverer: None,
255 genesis_verification_key: None,
256 origin_tag: None,
257 client_type: None,
258 #[cfg(feature = "fs")]
259 ancillary_verification_key: None,
260 aggregator_client: None,
261 certificate_verifier: None,
262 #[cfg(feature = "fs")]
263 http_file_downloader: None,
264 #[cfg(feature = "unstable")]
265 certificate_verifier_cache: None,
266 era_fetcher: None,
267 logger: None,
268 feedback_receivers: vec![],
269 options: ClientOptions::default(),
270 }
271 }
272
273 pub fn set_genesis_verification_key(
275 mut self,
276 genesis_verification_key: GenesisVerificationKey,
277 ) -> ClientBuilder {
278 self.genesis_verification_key = Some(genesis_verification_key);
279
280 self
281 }
282
283 #[cfg(not(target_family = "wasm"))]
285 pub fn with_capabilities(
286 mut self,
287 capabilities: RequiredAggregatorCapabilities,
288 ) -> ClientBuilder {
289 self.aggregator_capabilities = Some(capabilities);
290
291 self
292 }
293
294 #[cfg(not(target_family = "wasm"))]
296 pub fn with_aggregator_discoverer(
297 mut self,
298 discoverer: Arc<dyn AggregatorDiscoverer>,
299 ) -> ClientBuilder {
300 self.aggregator_discoverer = Some(discoverer);
301
302 self
303 }
304
305 pub fn build(self) -> MithrilResult<Client> {
310 let logger = self
311 .logger
312 .clone()
313 .unwrap_or_else(|| Logger::root(slog::Discard, o!()));
314
315 let genesis_verification_key = match self.genesis_verification_key {
316 Some(GenesisVerificationKey::JsonHex(ref key)) => key,
317 None => {
318 return Err(anyhow!(
319 "The genesis verification key must be provided to build the client with the 'set_genesis_verification_key' function"
320 ));
321 }
322 };
323
324 let feedback_sender = FeedbackSender::new(&self.feedback_receivers);
325
326 let aggregator_client = match self.aggregator_client {
327 None => Arc::new(self.build_aggregator_client(logger.clone())?),
328 Some(client) => client,
329 };
330
331 let mithril_era_client = match self.era_fetcher {
332 None => Arc::new(MithrilEraClient::new(Arc::new(
333 AggregatorHttpEraFetcher::new(aggregator_client.clone()),
334 ))),
335 Some(era_fetcher) => Arc::new(MithrilEraClient::new(era_fetcher)),
336 };
337
338 let certificate_verifier = match self.certificate_verifier {
339 None => Arc::new(
340 MithrilCertificateVerifier::new(
341 aggregator_client.clone(),
342 genesis_verification_key,
343 feedback_sender.clone(),
344 #[cfg(feature = "unstable")]
345 self.certificate_verifier_cache,
346 logger.clone(),
347 )
348 .with_context(|| "Building certificate verifier failed")?,
349 ),
350 Some(verifier) => verifier,
351 };
352 let certificate_client = Arc::new(CertificateClient::new(
353 aggregator_client.clone(),
354 certificate_verifier,
355 logger.clone(),
356 ));
357
358 let mithril_stake_distribution_client = Arc::new(MithrilStakeDistributionClient::new(
359 aggregator_client.clone(),
360 ));
361
362 #[cfg(feature = "fs")]
363 let http_file_downloader = match self.http_file_downloader {
364 None => Arc::new(RetryDownloader::new(
365 Arc::new(
366 HttpFileDownloader::new(feedback_sender.clone(), logger.clone())
367 .with_context(|| "Building http file downloader failed")?,
368 ),
369 FileDownloadRetryPolicy::default(),
370 )),
371 Some(http_file_downloader) => http_file_downloader,
372 };
373
374 #[cfg(feature = "fs")]
375 let ancillary_verifier = match self.ancillary_verification_key {
376 None => None,
377 Some(verification_key) => Some(Arc::new(AncillaryVerifier::new(
378 verification_key
379 .try_into()
380 .with_context(|| "Building ancillary verifier failed")?,
381 ))),
382 };
383
384 let snapshot_client = Arc::new(SnapshotClient::new(
385 aggregator_client.clone(),
386 #[cfg(feature = "fs")]
387 http_file_downloader.clone(),
388 #[cfg(feature = "fs")]
389 ancillary_verifier.clone(),
390 #[cfg(feature = "fs")]
391 feedback_sender.clone(),
392 #[cfg(feature = "fs")]
393 logger.clone(),
394 ));
395
396 let cardano_database_client = Arc::new(CardanoDatabaseClient::new(
397 aggregator_client.clone(),
398 #[cfg(feature = "fs")]
399 http_file_downloader,
400 #[cfg(feature = "fs")]
401 ancillary_verifier,
402 #[cfg(feature = "fs")]
403 feedback_sender,
404 #[cfg(feature = "fs")]
405 Arc::new(TimestampTempDirectoryProvider::new(&format!(
406 "{}",
407 Utc::now().timestamp_micros()
408 ))),
409 #[cfg(feature = "fs")]
410 logger,
411 ));
412
413 let cardano_transaction_client =
414 Arc::new(CardanoTransactionClient::new(aggregator_client.clone()));
415
416 let cardano_stake_distribution_client =
417 Arc::new(CardanoStakeDistributionClient::new(aggregator_client));
418
419 Ok(Client {
420 certificate_client,
421 mithril_stake_distribution_client,
422 snapshot_client,
423 cardano_database_client,
424 cardano_transaction_client,
425 cardano_stake_distribution_client,
426 mithril_era_client,
427 })
428 }
429
430 #[cfg(not(target_family = "wasm"))]
432 pub fn discover_aggregator(
433 &self,
434 network: &MithrilNetwork,
435 ) -> MithrilResult<impl Iterator<Item = AggregatorEndpoint>> {
436 let discoverer = self
437 .aggregator_discoverer
438 .clone()
439 .unwrap_or_else(|| Self::default_aggregator_discoverer());
440 let discoverer = if let Some(capabilities) = &self.aggregator_capabilities {
441 Arc::new(CapableAggregatorDiscoverer::new(
442 capabilities.to_owned(),
443 discoverer.clone(),
444 )) as Arc<dyn AggregatorDiscoverer>
445 } else {
446 discoverer as Arc<dyn AggregatorDiscoverer>
447 };
448
449 tokio::task::block_in_place(move || {
450 tokio::runtime::Handle::current().block_on(async move {
451 discoverer
452 .get_available_aggregators(network.to_owned())
453 .await
454 .with_context(|| "Discovering aggregator endpoint failed")
455 })
456 })
457 }
458
459 #[cfg(not(target_family = "wasm"))]
461 fn default_aggregator_discoverer() -> Arc<dyn AggregatorDiscoverer> {
462 Arc::new(ShuffleAggregatorDiscoverer::new(
463 Arc::new(HttpConfigAggregatorDiscoverer::default()),
464 {
465 let mut seed = [0u8; 32];
466 let timestamp = Utc::now().timestamp_nanos_opt().unwrap_or(0);
467 seed[..8].copy_from_slice(×tamp.to_le_bytes());
468
469 StdRng::from_seed(seed)
470 },
471 ))
472 }
473
474 fn build_aggregator_client(&self, logger: Logger) -> MithrilResult<AggregatorHTTPClient> {
475 let aggregator_endpoint = match self.aggregator_discovery {
476 AggregatorDiscoveryType::Url(ref url) => url.clone(),
477 #[cfg(not(target_family = "wasm"))]
478 AggregatorDiscoveryType::Automatic(ref network) => self
479 .discover_aggregator(network)?
480 .next()
481 .ok_or_else(|| anyhow!("No aggregator was available through discovery"))?
482 .into(),
483 };
484 let endpoint_url = Url::parse(&aggregator_endpoint).with_context(|| {
485 format!("Invalid aggregator endpoint, it must be a correctly formed url: '{aggregator_endpoint}'")
486 })?;
487
488 let headers = self.compute_http_headers();
489
490 AggregatorHTTPClient::new(
491 endpoint_url,
492 APIVersionProvider::compute_all_versions_sorted(),
493 logger,
494 Some(headers),
495 )
496 .with_context(|| "Building aggregator client failed")
497 }
498
499 fn compute_http_headers(&self) -> HashMap<String, String> {
500 let mut headers = self.options.http_headers.clone().unwrap_or_default();
501 if let Some(origin_tag) = self.origin_tag.clone() {
502 headers.insert(MITHRIL_ORIGIN_TAG_HEADER.to_string(), origin_tag);
503 }
504 if let Some(client_type) = self.client_type.clone() {
505 headers.insert(MITHRIL_CLIENT_TYPE_HEADER.to_string(), client_type);
506 } else if !headers.contains_key(MITHRIL_CLIENT_TYPE_HEADER) {
507 headers.insert(
508 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
509 DEFAULT_CLIENT_TYPE.to_string(),
510 );
511 }
512
513 headers
514 }
515
516 #[deprecated(since = "0.12.33", note = "Will be removed in 0.13.0")]
518 pub fn with_aggregator_client(
519 mut self,
520 aggregator_client: Arc<dyn AggregatorClient>,
521 ) -> ClientBuilder {
522 self.aggregator_client = Some(aggregator_client);
523 self
524 }
525
526 pub fn with_era_fetcher(mut self, era_fetcher: Arc<dyn EraFetcher>) -> ClientBuilder {
528 self.era_fetcher = Some(era_fetcher);
529 self
530 }
531
532 pub fn with_certificate_verifier(
534 mut self,
535 certificate_verifier: Arc<dyn CertificateVerifier>,
536 ) -> ClientBuilder {
537 self.certificate_verifier = Some(certificate_verifier);
538 self
539 }
540
541 cfg_unstable! {
542 pub fn with_certificate_verifier_cache(
546 mut self,
547 certificate_verifier_cache: Option<Arc<dyn CertificateVerifierCache>>,
548 ) -> ClientBuilder {
549 self.certificate_verifier_cache = certificate_verifier_cache;
550 self
551 }
552 }
553
554 cfg_fs! {
555 pub fn with_http_file_downloader(
557 mut self,
558 http_file_downloader: Arc<dyn FileDownloader>,
559 ) -> ClientBuilder {
560 self.http_file_downloader = Some(http_file_downloader);
561 self
562 }
563
564 pub fn set_ancillary_verification_key<T: Into<Option<String>>>(
566 mut self,
567 ancillary_verification_key: T,
568 ) -> ClientBuilder {
569 self.ancillary_verification_key = ancillary_verification_key.into();
570 self
571 }
572 }
573
574 pub fn with_logger(mut self, logger: Logger) -> Self {
576 self.logger = Some(logger);
577 self
578 }
579
580 pub fn with_origin_tag(mut self, origin_tag: Option<String>) -> Self {
582 self.origin_tag = origin_tag;
583 self
584 }
585
586 pub fn with_client_type(mut self, client_type: Option<String>) -> Self {
588 self.client_type = client_type;
589 self
590 }
591
592 pub fn with_options(mut self, options: ClientOptions) -> Self {
594 self.options = options;
595 self
596 }
597
598 pub fn add_feedback_receiver(mut self, receiver: Arc<dyn FeedbackReceiver>) -> Self {
602 self.feedback_receivers.push(receiver);
603 self
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610
611 fn default_headers() -> HashMap<String, String> {
612 HashMap::from([(
613 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
614 DEFAULT_CLIENT_TYPE.to_string(),
615 )])
616 }
617
618 #[tokio::test]
619 async fn compute_http_headers_returns_options_http_headers() {
620 let http_headers = default_headers();
621 let client_builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
622 .with_options(ClientOptions {
623 http_headers: Some(http_headers.clone()),
624 });
625
626 let computed_headers = client_builder.compute_http_headers();
627
628 assert_eq!(computed_headers, http_headers);
629 }
630
631 #[tokio::test]
632 async fn compute_http_headers_with_origin_tag_returns_options_http_headers_with_origin_tag() {
633 let http_headers = default_headers();
634 let client_builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
635 .with_options(ClientOptions {
636 http_headers: Some(http_headers.clone()),
637 })
638 .with_origin_tag(Some("CLIENT_TAG".to_string()));
639 let mut expected_headers = http_headers.clone();
640 expected_headers.insert(
641 MITHRIL_ORIGIN_TAG_HEADER.to_string(),
642 "CLIENT_TAG".to_string(),
643 );
644
645 let computed_headers = client_builder.compute_http_headers();
646 assert_eq!(computed_headers, expected_headers);
647 }
648
649 #[tokio::test]
650 async fn test_with_origin_tag_not_overwrite_other_client_options_attributes() {
651 let builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
652 .with_options(ClientOptions { http_headers: None })
653 .with_origin_tag(Some("TEST".to_string()));
654 assert_eq!(None, builder.options.http_headers);
655 assert_eq!(Some("TEST".to_string()), builder.origin_tag);
656
657 let http_headers = HashMap::from([("Key".to_string(), "Value".to_string())]);
658 let builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
659 .with_options(ClientOptions {
660 http_headers: Some(http_headers.clone()),
661 })
662 .with_origin_tag(Some("TEST".to_string()));
663 assert_eq!(Some(http_headers), builder.options.http_headers);
664 assert_eq!(Some("TEST".to_string()), builder.origin_tag);
665 }
666
667 #[tokio::test]
668 async fn test_with_origin_tag_can_be_unset() {
669 let http_headers = HashMap::from([("Key".to_string(), "Value".to_string())]);
670 let client_options = ClientOptions {
671 http_headers: Some(http_headers.clone()),
672 };
673 let builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
674 .with_options(client_options)
675 .with_origin_tag(None);
676
677 assert_eq!(Some(http_headers), builder.options.http_headers);
678 assert_eq!(None, builder.origin_tag);
679 }
680
681 #[tokio::test]
682 async fn compute_http_headers_with_client_type_returns_options_http_headers_with_client_type() {
683 let http_headers = HashMap::from([("Key".to_string(), "Value".to_string())]);
684 let client_builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
685 .with_options(ClientOptions {
686 http_headers: Some(http_headers.clone()),
687 })
688 .with_client_type(Some("CLIENT_TYPE".to_string()));
689
690 let computed_headers = client_builder.compute_http_headers();
691
692 assert_eq!(
693 computed_headers,
694 HashMap::from([
695 ("Key".to_string(), "Value".to_string()),
696 (
697 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
698 "CLIENT_TYPE".to_string()
699 )
700 ])
701 );
702 }
703
704 #[tokio::test]
705 async fn compute_http_headers_with_options_containing_client_type_returns_client_type() {
706 let http_headers = HashMap::from([(
707 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
708 "client type from options".to_string(),
709 )]);
710 let client_builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
711 .with_options(ClientOptions {
712 http_headers: Some(http_headers.clone()),
713 });
714
715 let computed_headers = client_builder.compute_http_headers();
716
717 assert_eq!(computed_headers, http_headers);
718 }
719
720 #[tokio::test]
721 async fn test_with_client_type_not_overwrite_other_client_options_attributes() {
722 let builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
723 .with_options(ClientOptions { http_headers: None })
724 .with_client_type(Some("TEST".to_string()));
725 assert_eq!(None, builder.options.http_headers);
726 assert_eq!(Some("TEST".to_string()), builder.client_type);
727
728 let http_headers = HashMap::from([("Key".to_string(), "Value".to_string())]);
729 let builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
730 .with_options(ClientOptions {
731 http_headers: Some(http_headers.clone()),
732 })
733 .with_client_type(Some("TEST".to_string()));
734 assert_eq!(Some(http_headers), builder.options.http_headers);
735 assert_eq!(Some("TEST".to_string()), builder.client_type);
736 }
737
738 #[tokio::test]
739 async fn test_given_a_none_client_type_compute_http_headers_will_set_client_type_to_default_value()
740 {
741 let builder_without_client_type =
742 ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()));
743 let computed_headers = builder_without_client_type.compute_http_headers();
744
745 assert_eq!(
746 computed_headers,
747 HashMap::from([(
748 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
749 DEFAULT_CLIENT_TYPE.to_string()
750 )])
751 );
752
753 let builder_with_none_client_type =
754 ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string())).with_client_type(None);
755 let computed_headers = builder_with_none_client_type.compute_http_headers();
756
757 assert_eq!(
758 computed_headers,
759 HashMap::from([(
760 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
761 DEFAULT_CLIENT_TYPE.to_string()
762 )])
763 );
764 }
765
766 #[tokio::test]
767 async fn test_compute_http_headers_will_compute_client_type_header_from_struct_attribute_over_options()
768 {
769 let http_headers = HashMap::from([(
770 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
771 "client type from options".to_string(),
772 )]);
773 let client_builder = ClientBuilder::new(AggregatorDiscoveryType::Url("".to_string()))
774 .with_options(ClientOptions {
775 http_headers: Some(http_headers.clone()),
776 })
777 .with_client_type(Some("client type".to_string()));
778
779 let computed_headers = client_builder.compute_http_headers();
780
781 assert_eq!(
782 computed_headers,
783 HashMap::from([(
784 MITHRIL_CLIENT_TYPE_HEADER.to_string(),
785 "client type".to_string()
786 )])
787 );
788 }
789}