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