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