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