1use super::{InheritanceResolver, NetworkFileConfig, NetworkFileLoader, NetworksSource};
14use crate::config::config_file::ConfigFileNetworkType;
15use crate::config::ConfigFileError;
16use serde::de::{self, Deserializer};
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19use std::ops::Index;
20
21#[derive(Debug, Default, Serialize, Clone)]
26pub struct NetworksFileConfig {
27 pub networks: Vec<NetworkFileConfig>,
28 #[serde(skip)]
29 network_map: HashMap<(ConfigFileNetworkType, String), usize>,
30}
31
32impl<'de> Deserialize<'de> for NetworksFileConfig {
38 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39 where
40 D: Deserializer<'de>,
41 {
42 let source_option: Option<NetworksSource> = Option::deserialize(deserializer)?;
44 let source = source_option.unwrap_or_default();
45
46 let final_networks =
47 NetworkFileLoader::load_from_source(source).map_err(de::Error::custom)?;
48
49 if final_networks.is_empty() {
51 return Err(de::Error::custom(
52 "NetworksFileConfig cannot be empty - networks must contain at least one network configuration"
53 ));
54 }
55
56 let unflattened_config = NetworksFileConfig::new(final_networks).map_err(|e| {
59 de::Error::custom(format!("Error creating initial NetworksFileConfig: {e:?}"))
60 })?;
61
62 unflattened_config
64 .flatten()
65 .map_err(|e| de::Error::custom(format!("Error flattening NetworksFileConfig: {e:?}")))
66 }
67}
68
69impl NetworksFileConfig {
70 pub fn new(networks: Vec<NetworkFileConfig>) -> Result<Self, ConfigFileError> {
76 let mut network_map = HashMap::new();
77
78 for (index, network) in networks.iter().enumerate() {
80 let name = network.network_name();
81 let network_type = network.network_type();
82 let key = (network_type, name.to_string());
83
84 if network_map.insert(key, index).is_some() {
85 return Err(ConfigFileError::DuplicateId(format!(
87 "{network_type:?} network '{name}'"
88 )));
89 }
90 }
91
92 let instance = Self {
93 networks,
94 network_map,
95 };
96
97 for network in &instance.networks {
99 if network.inherits_from().is_some() {
100 instance.trace_inheritance(network.network_name(), network.network_type())?;
101 }
102 }
103
104 Ok(instance)
105 }
106
107 pub fn get_network(
117 &self,
118 network_type: ConfigFileNetworkType,
119 name: &str,
120 ) -> Option<&NetworkFileConfig> {
121 let key = (network_type, name.to_string());
122 self.network_map
123 .get(&key)
124 .map(|&index| &self.networks[index])
125 }
126
127 pub fn flatten(&self) -> Result<NetworksFileConfig, ConfigFileError> {
137 let resolved_networks = self
139 .networks
140 .iter()
141 .map(|network| self.resolve_inheritance(network))
142 .collect::<Result<Vec<NetworkFileConfig>, ConfigFileError>>()?;
143
144 NetworksFileConfig::new(resolved_networks)
145 }
146
147 fn resolve_inheritance(
156 &self,
157 network: &NetworkFileConfig,
158 ) -> Result<NetworkFileConfig, ConfigFileError> {
159 if network.inherits_from().is_none() {
161 return Ok(network.clone());
162 }
163
164 let parent_name = network.inherits_from().unwrap();
165 let network_name = network.network_name();
166 let network_type = network.network_type();
167
168 let lookup_fn = move |name: &str| self.get_network(network_type, name);
170 let resolver = InheritanceResolver::new(&lookup_fn);
171
172 match network {
173 NetworkFileConfig::Evm(config) => {
174 let resolved_config =
175 resolver.resolve_evm_inheritance(config, network_name, parent_name)?;
176 Ok(NetworkFileConfig::Evm(resolved_config))
177 }
178 NetworkFileConfig::Solana(config) => {
179 let resolved_config =
180 resolver.resolve_solana_inheritance(config, network_name, parent_name)?;
181 Ok(NetworkFileConfig::Solana(resolved_config))
182 }
183 NetworkFileConfig::Stellar(config) => {
184 let resolved_config =
185 resolver.resolve_stellar_inheritance(config, network_name, parent_name)?;
186 Ok(NetworkFileConfig::Stellar(resolved_config))
187 }
188 }
189 }
190
191 pub fn validate(&self) -> Result<(), ConfigFileError> {
198 for network in &self.networks {
199 network.validate()?;
200 }
201 Ok(())
202 }
203
204 fn trace_inheritance(
214 &self,
215 start_network_name: &str,
216 network_type: ConfigFileNetworkType,
217 ) -> Result<(), ConfigFileError> {
218 let mut current_path_names = Vec::new();
219 let mut current_name = start_network_name;
220
221 loop {
222 if current_path_names.contains(¤t_name) {
224 let cycle_path_str = current_path_names.join(" -> ");
225 return Err(ConfigFileError::CircularInheritance(format!(
226 "Circular inheritance detected: {cycle_path_str} -> {current_name}"
227 )));
228 }
229
230 current_path_names.push(current_name);
231
232 let current_network =
233 self.get_network(network_type, current_name)
234 .ok_or_else(|| {
235 ConfigFileError::InvalidReference(format!(
236 "{network_type:?} network '{current_name}' not found in configuration"
237 ))
238 })?;
239
240 if let Some(source_name) = current_network.inherits_from() {
241 let derived_type = current_network.network_type();
242
243 if source_name == current_name {
244 return Err(ConfigFileError::InvalidReference(format!(
245 "Network '{current_name}' cannot inherit from itself"
246 )));
247 }
248
249 let source_network =
250 self.get_network(network_type, source_name).ok_or_else(|| {
251 ConfigFileError::InvalidReference(format!(
252 "{network_type:?} network '{current_name}' inherits from non-existent network '{source_name}'"
253 ))
254 })?;
255
256 let source_type = source_network.network_type();
257
258 if derived_type != source_type {
259 return Err(ConfigFileError::IncompatibleInheritanceType(format!(
260 "Network '{current_name}' (type {derived_type:?}) tries to inherit from '{source_name}' (type {source_type:?})"
261 )));
262 }
263 current_name = source_name;
264 } else {
265 break;
266 }
267 }
268
269 Ok(())
270 }
271
272 pub fn iter(&self) -> impl Iterator<Item = &NetworkFileConfig> {
274 self.networks.iter()
275 }
276
277 pub fn len(&self) -> usize {
279 self.networks.len()
280 }
281
282 pub fn is_empty(&self) -> bool {
284 self.networks.is_empty()
285 }
286
287 pub fn networks_by_type(
289 &self,
290 network_type: crate::config::config_file::ConfigFileNetworkType,
291 ) -> impl Iterator<Item = &NetworkFileConfig> {
292 self.networks
293 .iter()
294 .filter(move |network| network.network_type() == network_type)
295 }
296
297 pub fn network_names(&self) -> impl Iterator<Item = &str> {
299 self.networks.iter().map(|network| network.network_name())
300 }
301
302 pub fn first(&self) -> Option<&NetworkFileConfig> {
308 self.networks.first()
309 }
310
311 pub fn get(&self, index: usize) -> Option<&NetworkFileConfig> {
320 self.networks.get(index)
321 }
322}
323
324impl Index<usize> for NetworksFileConfig {
326 type Output = NetworkFileConfig;
327
328 fn index(&self, index: usize) -> &Self::Output {
329 &self.networks[index]
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::config::config_file::network::test_utils::*;
337 use crate::config::config_file::ConfigFileNetworkType;
338 use std::fs::File;
339 use std::io::Write;
340 use tempfile::tempdir;
341
342 #[test]
343 fn test_new_with_single_network() {
344 let networks = vec![create_evm_network_wrapped("test-evm")];
345 let config = NetworksFileConfig::new(networks);
346
347 assert!(config.is_ok());
348 let config = config.unwrap();
349 assert_eq!(config.networks.len(), 1);
350 assert_eq!(config.network_map.len(), 1);
351 assert!(config
352 .network_map
353 .contains_key(&(ConfigFileNetworkType::Evm, "test-evm".to_string())));
354 }
355
356 #[test]
357 fn test_new_with_multiple_networks() {
358 let networks = vec![
359 create_evm_network_wrapped("evm-1"),
360 create_solana_network_wrapped("solana-1"),
361 create_stellar_network_wrapped("stellar-1"),
362 ];
363 let config = NetworksFileConfig::new(networks);
364
365 assert!(config.is_ok());
366 let config = config.unwrap();
367 assert_eq!(config.networks.len(), 3);
368 assert_eq!(config.network_map.len(), 3);
369 assert!(config
370 .network_map
371 .contains_key(&(ConfigFileNetworkType::Evm, "evm-1".to_string())));
372 assert!(config
373 .network_map
374 .contains_key(&(ConfigFileNetworkType::Solana, "solana-1".to_string())));
375 assert!(config
376 .network_map
377 .contains_key(&(ConfigFileNetworkType::Stellar, "stellar-1".to_string())));
378 }
379
380 #[test]
381 fn test_new_with_empty_networks() {
382 let networks = vec![];
383 let config = NetworksFileConfig::new(networks);
384
385 assert!(config.is_ok());
386 let config = config.unwrap();
387 assert_eq!(config.networks.len(), 0);
388 assert_eq!(config.network_map.len(), 0);
389 }
390
391 #[test]
392 fn test_new_with_valid_inheritance() {
393 let networks = vec![
394 create_evm_network_wrapped("parent"),
395 create_evm_network_wrapped_with_parent("child", "parent"),
396 ];
397 let config = NetworksFileConfig::new(networks);
398
399 assert!(config.is_ok());
400 let config = config.unwrap();
401 assert_eq!(config.networks.len(), 2);
402 }
403
404 #[test]
405 fn test_new_with_invalid_inheritance_reference() {
406 let networks = vec![create_evm_network_wrapped_with_parent(
407 "child",
408 "non-existent",
409 )];
410 let result = NetworksFileConfig::new(networks);
411
412 assert!(result.is_err());
413 assert!(matches!(
414 result.unwrap_err(),
415 ConfigFileError::InvalidReference(_)
416 ));
417 }
418
419 #[test]
420 fn test_new_with_self_inheritance() {
421 let networks = vec![create_evm_network_wrapped_with_parent(
422 "self-ref", "self-ref",
423 )];
424 let result = NetworksFileConfig::new(networks);
425
426 assert!(result.is_err());
427 assert!(matches!(
428 result.unwrap_err(),
429 ConfigFileError::InvalidReference(_)
430 ));
431 }
432
433 #[test]
434 fn test_new_with_circular_inheritance() {
435 let networks = vec![
436 create_evm_network_wrapped_with_parent("a", "b"),
437 create_evm_network_wrapped_with_parent("b", "c"),
438 create_evm_network_wrapped_with_parent("c", "a"),
439 ];
440 let result = NetworksFileConfig::new(networks);
441
442 assert!(result.is_err());
443 assert!(matches!(
444 result.unwrap_err(),
445 ConfigFileError::CircularInheritance(_)
446 ));
447 }
448
449 #[test]
450 fn test_new_with_incompatible_inheritance_types() {
451 let networks = vec![
452 create_evm_network_wrapped("evm-parent"),
453 create_solana_network_wrapped_with_parent("solana-child", "evm-parent"),
454 ];
455 let result = NetworksFileConfig::new(networks);
456
457 assert!(result.is_err());
458 let error = result.unwrap_err();
459 assert!(matches!(error, ConfigFileError::InvalidReference(_)));
460 }
461
462 #[test]
463 fn test_new_with_deep_inheritance_chain() {
464 let networks = vec![
465 create_evm_network_wrapped("root"),
466 create_evm_network_wrapped_with_parent("level1", "root"),
467 create_evm_network_wrapped_with_parent("level2", "level1"),
468 create_evm_network_wrapped_with_parent("level3", "level2"),
469 ];
470 let config = NetworksFileConfig::new(networks);
471
472 assert!(config.is_ok());
473 let config = config.unwrap();
474 assert_eq!(config.networks.len(), 4);
475 }
476
477 #[test]
478 fn test_get_network_existing() {
479 let networks = vec![
480 create_evm_network_wrapped("test-evm"),
481 create_solana_network_wrapped("test-solana"),
482 ];
483 let config = NetworksFileConfig::new(networks).unwrap();
484
485 let network = config.get_network(ConfigFileNetworkType::Evm, "test-evm");
486 assert!(network.is_some());
487 assert_eq!(network.unwrap().network_name(), "test-evm");
488
489 let network = config.get_network(ConfigFileNetworkType::Solana, "test-solana");
490 assert!(network.is_some());
491 assert_eq!(network.unwrap().network_name(), "test-solana");
492 }
493
494 #[test]
495 fn test_get_network_non_existent() {
496 let networks = vec![create_evm_network_wrapped("test-evm")];
497 let config = NetworksFileConfig::new(networks).unwrap();
498
499 let network = config.get_network(ConfigFileNetworkType::Evm, "non-existent");
500 assert!(network.is_none());
501 }
502
503 #[test]
504 fn test_get_network_empty_config() {
505 let config = NetworksFileConfig::new(vec![]).unwrap();
506
507 let network = config.get_network(ConfigFileNetworkType::Evm, "any-name");
508 assert!(network.is_none());
509 }
510
511 #[test]
512 fn test_get_network_case_sensitive() {
513 let networks = vec![create_evm_network_wrapped("Test-Network")];
514 let config = NetworksFileConfig::new(networks).unwrap();
515
516 assert!(config
517 .get_network(ConfigFileNetworkType::Evm, "Test-Network")
518 .is_some());
519 assert!(config
520 .get_network(ConfigFileNetworkType::Evm, "test-network")
521 .is_none());
522 assert!(config
523 .get_network(ConfigFileNetworkType::Evm, "TEST-NETWORK")
524 .is_none());
525 }
526
527 #[test]
528 fn test_validate_success() {
529 let networks = vec![
530 create_evm_network_wrapped("evm-1"),
531 create_solana_network_wrapped("solana-1"),
532 ];
533 let config = NetworksFileConfig::new(networks).unwrap();
534
535 let result = config.validate();
536 assert!(result.is_ok());
537 }
538
539 #[test]
540 fn test_validate_with_invalid_network() {
541 let networks = vec![
542 create_evm_network_wrapped("valid"),
543 create_invalid_evm_network_wrapped("invalid"),
544 ];
545 let config = NetworksFileConfig::new(networks).unwrap();
546
547 let result = config.validate();
548 assert!(result.is_err());
549 assert!(matches!(
550 result.unwrap_err(),
551 ConfigFileError::MissingField(_)
552 ));
553 }
554
555 #[test]
556 fn test_validate_empty_config() {
557 let config = NetworksFileConfig::new(vec![]).unwrap();
558
559 let result = config.validate();
560 assert!(result.is_ok()); }
562
563 #[test]
564 fn test_flatten_without_inheritance() {
565 let networks = vec![
566 create_evm_network_wrapped("evm-1"),
567 create_solana_network_wrapped("solana-1"),
568 ];
569 let config = NetworksFileConfig::new(networks).unwrap();
570
571 let flattened = config.flatten();
572 assert!(flattened.is_ok());
573 let flattened = flattened.unwrap();
574 assert_eq!(flattened.networks.len(), 2);
575 }
576
577 #[test]
578 fn test_flatten_with_simple_inheritance() {
579 let networks = vec![
580 create_evm_network_wrapped("parent"),
581 create_evm_network_wrapped_with_parent("child", "parent"),
582 ];
583 let config = NetworksFileConfig::new(networks).unwrap();
584
585 let flattened = config.flatten();
586 assert!(flattened.is_ok());
587 let flattened = flattened.unwrap();
588 assert_eq!(flattened.networks.len(), 2);
589
590 let child = flattened.get_network(ConfigFileNetworkType::Evm, "child");
592 assert!(child.is_some());
593 assert_eq!(child.unwrap().inherits_from(), Some("parent"));
595 }
596
597 #[test]
598 fn test_flatten_with_multi_level_inheritance() {
599 let networks = vec![
600 create_evm_network_wrapped("root"),
601 create_evm_network_wrapped_with_parent("middle", "root"),
602 create_evm_network_wrapped_with_parent("leaf", "middle"),
603 ];
604 let config = NetworksFileConfig::new(networks).unwrap();
605
606 let flattened = config.flatten();
607 assert!(flattened.is_ok());
608 let flattened = flattened.unwrap();
609 assert_eq!(flattened.networks.len(), 3);
610 }
611
612 #[test]
613 fn test_validation_after_flatten_with_failure() {
614 let networks = vec![
615 create_evm_network_wrapped("valid"),
616 create_invalid_evm_network_wrapped("invalid"),
617 ];
618 let config = NetworksFileConfig::new(networks).unwrap();
619
620 let flattened = config.flatten();
621 assert!(flattened.is_ok());
622 let flattened = flattened.unwrap();
623
624 let result = flattened.validate();
625
626 assert!(result.is_err());
627 assert!(matches!(
628 result.unwrap_err(),
629 ConfigFileError::MissingField(_)
630 ));
631 }
632
633 #[test]
634 fn test_flatten_with_mixed_network_types() {
635 let networks = vec![
636 create_evm_network_wrapped("evm-parent"),
637 create_evm_network_wrapped_with_parent("evm-child", "evm-parent"),
638 create_solana_network_wrapped("solana-parent"),
639 create_solana_network_wrapped_with_parent("solana-child", "solana-parent"),
640 create_stellar_network_wrapped("stellar-standalone"),
641 ];
642 let config = NetworksFileConfig::new(networks).unwrap();
643
644 let flattened = config.flatten();
645 assert!(flattened.is_ok());
646 let flattened = flattened.unwrap();
647 assert_eq!(flattened.networks.len(), 5);
648 }
649
650 #[test]
651 fn test_iter() {
652 let networks = vec![
653 create_evm_network_wrapped("evm-1"),
654 create_solana_network_wrapped("solana-1"),
655 ];
656 let config = NetworksFileConfig::new(networks).unwrap();
657
658 let collected: Vec<_> = config.iter().collect();
659 assert_eq!(collected.len(), 2);
660 assert_eq!(collected[0].network_name(), "evm-1");
661 assert_eq!(collected[1].network_name(), "solana-1");
662 }
663
664 #[test]
665 fn test_len() {
666 let config = NetworksFileConfig::new(vec![]).unwrap();
667 assert_eq!(config.len(), 0);
668
669 let networks = vec![create_evm_network_wrapped("test")];
670 let config = NetworksFileConfig::new(networks).unwrap();
671 assert_eq!(config.len(), 1);
672
673 let networks = vec![
674 create_evm_network_wrapped("test1"),
675 create_solana_network_wrapped("test2"),
676 create_stellar_network_wrapped("test3"),
677 ];
678 let config = NetworksFileConfig::new(networks).unwrap();
679 assert_eq!(config.len(), 3);
680 }
681
682 #[test]
683 fn test_is_empty() {
684 let config = NetworksFileConfig::new(vec![]).unwrap();
685 assert!(config.is_empty());
686
687 let networks = vec![create_evm_network_wrapped("test")];
688 let config = NetworksFileConfig::new(networks).unwrap();
689 assert!(!config.is_empty());
690 }
691
692 #[test]
693 fn test_networks_by_type() {
694 let networks = vec![
695 create_evm_network_wrapped("evm-1"),
696 create_evm_network_wrapped("evm-2"),
697 create_solana_network_wrapped("solana-1"),
698 create_stellar_network_wrapped("stellar-1"),
699 ];
700 let config = NetworksFileConfig::new(networks).unwrap();
701
702 let evm_networks: Vec<_> = config
703 .networks_by_type(ConfigFileNetworkType::Evm)
704 .collect();
705 assert_eq!(evm_networks.len(), 2);
706
707 let solana_networks: Vec<_> = config
708 .networks_by_type(ConfigFileNetworkType::Solana)
709 .collect();
710 assert_eq!(solana_networks.len(), 1);
711
712 let stellar_networks: Vec<_> = config
713 .networks_by_type(ConfigFileNetworkType::Stellar)
714 .collect();
715 assert_eq!(stellar_networks.len(), 1);
716 }
717
718 #[test]
719 fn test_networks_by_type_empty_result() {
720 let networks = vec![create_evm_network_wrapped("evm-only")];
721 let config = NetworksFileConfig::new(networks).unwrap();
722
723 let solana_networks: Vec<_> = config
724 .networks_by_type(ConfigFileNetworkType::Solana)
725 .collect();
726 assert_eq!(solana_networks.len(), 0);
727 }
728
729 #[test]
730 fn test_network_names() {
731 let networks = vec![
732 create_evm_network_wrapped("alpha"),
733 create_solana_network_wrapped("beta"),
734 create_stellar_network_wrapped("gamma"),
735 ];
736 let config = NetworksFileConfig::new(networks).unwrap();
737
738 let names: Vec<_> = config.network_names().collect();
739 assert_eq!(names.len(), 3);
740 assert!(names.contains(&"alpha"));
741 assert!(names.contains(&"beta"));
742 assert!(names.contains(&"gamma"));
743 }
744
745 #[test]
746 fn test_network_names_empty() {
747 let config = NetworksFileConfig::new(vec![]).unwrap();
748
749 let names: Vec<_> = config.network_names().collect();
750 assert_eq!(names.len(), 0);
751 }
752
753 #[test]
755 fn test_default() {
756 let config = NetworksFileConfig::default();
757
758 assert_eq!(config.networks.len(), 0);
759 assert_eq!(config.network_map.len(), 0);
760 assert!(config.is_empty());
761 }
762
763 #[test]
764 fn test_deserialize_from_array() {
765 let json = r#"[
766 {
767 "type": "evm",
768 "network": "test-evm",
769 "chain_id": 31337,
770 "required_confirmations": 1,
771 "symbol": "ETH",
772 "rpc_urls": ["https://rpc.test.example.com"]
773 }
774 ]"#;
775
776 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
777 assert!(result.is_ok());
778 let config = result.unwrap();
779 assert_eq!(config.len(), 1);
780 assert!(config
781 .get_network(ConfigFileNetworkType::Evm, "test-evm")
782 .is_some());
783 }
784
785 #[test]
786 fn test_deserialize_empty_array_returns_error() {
787 let json = r#"[]"#;
788 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
789
790 assert!(result.is_err());
791 let error_message = result.unwrap_err().to_string();
792 assert!(error_message.contains("NetworksFileConfig cannot be empty"));
793 }
794
795 #[test]
796 fn test_deserialize_from_directory() {
797 let dir = tempdir().expect("Failed to create temp dir");
798 let network_dir_path = dir.path();
799
800 let evm_file = network_dir_path.join("evm.json");
802 let mut file = File::create(&evm_file).expect("Failed to create EVM file");
803 writeln!(file, r#"{{"networks": [{{"type": "evm", "network": "test-evm-from-file", "chain_id": 31337, "required_confirmations": 1, "symbol": "ETH", "rpc_urls": ["https://rpc.test.example.com"]}}]}}"#).expect("Failed to write EVM file");
804
805 let solana_file = network_dir_path.join("solana.json");
806 let mut file = File::create(&solana_file).expect("Failed to create Solana file");
807 writeln!(file, r#"{{"networks": [{{"type": "solana", "network": "test-solana-from-file", "rpc_urls": ["https://rpc.solana.example.com"]}}]}}"#).expect("Failed to write Solana file");
808
809 let json = format!(r#""{}""#, network_dir_path.to_str().unwrap());
810
811 let result: Result<NetworksFileConfig, _> = serde_json::from_str(&json);
812 assert!(result.is_ok());
813 let config = result.unwrap();
814 assert_eq!(config.len(), 2);
815 assert!(config
816 .get_network(ConfigFileNetworkType::Evm, "test-evm-from-file")
817 .is_some());
818 assert!(config
819 .get_network(ConfigFileNetworkType::Solana, "test-solana-from-file")
820 .is_some());
821 }
822
823 #[test]
824 fn test_deserialize_invalid_directory() {
825 let json = r#""/non/existent/directory""#;
826
827 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
828 assert!(result.is_err());
829 }
830
831 #[test]
832 fn test_deserialize_networks_array() {
833 let json = r#"[
834 {
835 "type": "evm",
836 "network": "testnet",
837 "chain_id": 11155111,
838 "required_confirmations": 6,
839 "symbol": "ETH",
840 "rpc_urls": ["https://sepolia.drpc.org", "https://1rpc.io/sepolia"]
841 },
842 {
843 "type": "solana",
844 "network": "devnet",
845 "rpc_urls": ["https://api.devnet.solana.com"],
846 "explorer_urls": ["https://explorer.solana.com?cluster=devnet"],
847 "average_blocktime_ms": 400,
848 "is_testnet": true
849 }
850 ]"#;
851
852 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
853 assert!(result.is_ok());
854 let config = result.unwrap();
855 assert_eq!(config.len(), 2);
856
857 let evm_network = config
859 .get_network(ConfigFileNetworkType::Evm, "testnet")
860 .unwrap();
861 if let NetworkFileConfig::Evm(evm_config) = evm_network {
862 assert_eq!(evm_config.chain_id, Some(11155111));
863 assert!(evm_config.common.rpc_urls.is_some());
864 assert_eq!(evm_config.common.rpc_urls.as_ref().unwrap().len(), 2);
865 }
866
867 let solana_network = config
869 .get_network(ConfigFileNetworkType::Solana, "devnet")
870 .unwrap();
871 if let NetworkFileConfig::Solana(solana_config) = solana_network {
872 assert_eq!(solana_config.common.average_blocktime_ms, Some(400));
873 assert_eq!(solana_config.common.is_testnet, Some(true));
874 }
875 }
876
877 #[test]
878 fn test_deserialize_with_invalid_inheritance() {
879 let json = r#"[
880 {
881 "type": "evm",
882 "network": "child",
883 "from": "non-existent-parent",
884 "chain_id": 31337,
885 "required_confirmations": 1,
886 "symbol": "ETH",
887 "rpc_urls": ["https://rpc.test.example.com"]
888 }
889 ]"#;
890
891 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
892 assert!(result.is_err());
893 }
894
895 #[test]
897 fn test_large_number_of_networks() {
898 let mut networks = Vec::new();
899 for i in 0..100 {
900 networks.push(create_evm_network_wrapped(&format!("network-{i}")));
901 }
902
903 let config = NetworksFileConfig::new(networks);
904 assert!(config.is_ok());
905 let config = config.unwrap();
906 assert_eq!(config.len(), 100);
907
908 for i in 0..100 {
910 assert!(config
911 .get_network(ConfigFileNetworkType::Evm, &format!("network-{i}"))
912 .is_some());
913 }
914 }
915
916 #[test]
917 fn test_unicode_network_names() {
918 let networks = vec![
919 create_evm_network_wrapped("测试网络"),
920 create_solana_network_wrapped("тестовая-сеть"),
921 create_stellar_network_wrapped("réseau-test"),
922 ];
923
924 let config = NetworksFileConfig::new(networks);
925 assert!(config.is_ok());
926 let config = config.unwrap();
927 assert_eq!(config.len(), 3);
928 assert!(config
929 .get_network(ConfigFileNetworkType::Evm, "测试网络")
930 .is_some());
931 assert!(config
932 .get_network(ConfigFileNetworkType::Solana, "тестовая-сеть")
933 .is_some());
934 assert!(config
935 .get_network(ConfigFileNetworkType::Stellar, "réseau-test")
936 .is_some());
937 }
938
939 #[test]
940 fn test_special_characters_in_network_names() {
941 let networks = vec![
942 create_evm_network_wrapped("test-network_123"),
943 create_solana_network_wrapped("test.network.with.dots"),
944 create_stellar_network_wrapped("test@network#with$symbols"),
945 ];
946
947 let config = NetworksFileConfig::new(networks);
948 assert!(config.is_ok());
949 let config = config.unwrap();
950 assert_eq!(config.len(), 3);
951 }
952
953 #[test]
954 fn test_very_long_network_names() {
955 let long_name = "a".repeat(1000);
956 let networks = vec![create_evm_network_wrapped(&long_name)];
957
958 let config = NetworksFileConfig::new(networks);
959 assert!(config.is_ok());
960 let config = config.unwrap();
961 assert!(config
962 .get_network(ConfigFileNetworkType::Evm, &long_name)
963 .is_some());
964 }
965
966 #[test]
967 fn test_complex_inheritance_scenario() {
968 let networks = vec![
969 create_evm_network_wrapped("evm-root"),
971 create_solana_network_wrapped("solana-root"),
972 create_evm_network_wrapped_with_parent("evm-child1", "evm-root"),
974 create_evm_network_wrapped_with_parent("evm-child2", "evm-root"),
975 create_solana_network_wrapped_with_parent("solana-child1", "solana-root"),
976 create_evm_network_wrapped_with_parent("evm-grandchild", "evm-child1"),
978 ];
979
980 let config = NetworksFileConfig::new(networks);
981 assert!(config.is_ok());
982 let config = config.unwrap();
983 assert_eq!(config.len(), 6);
984
985 let flattened = config.flatten();
986 assert!(flattened.is_ok());
987 let flattened = flattened.unwrap();
988 assert_eq!(flattened.len(), 6);
989 }
990
991 #[test]
992 fn test_new_with_duplicate_network_names_across_types() {
993 let networks = vec![
995 create_evm_network_wrapped("mainnet"),
996 create_solana_network_wrapped("mainnet"),
997 create_stellar_network_wrapped("mainnet"),
998 ];
999 let result = NetworksFileConfig::new(networks);
1000
1001 assert!(result.is_ok());
1002 let config = result.unwrap();
1003 assert_eq!(config.networks.len(), 3);
1004 assert_eq!(config.network_map.len(), 3);
1005
1006 assert!(config
1008 .get_network(ConfigFileNetworkType::Evm, "mainnet")
1009 .is_some());
1010 assert!(config
1011 .get_network(ConfigFileNetworkType::Solana, "mainnet")
1012 .is_some());
1013 assert!(config
1014 .get_network(ConfigFileNetworkType::Stellar, "mainnet")
1015 .is_some());
1016 }
1017
1018 #[test]
1019 fn test_new_with_duplicate_network_names_within_same_type() {
1020 let networks = vec![
1021 create_evm_network_wrapped("duplicate-evm"),
1022 create_evm_network_wrapped("duplicate-evm"),
1023 ];
1024 let result = NetworksFileConfig::new(networks);
1025
1026 assert!(result.is_err());
1027 assert!(matches!(
1028 result.unwrap_err(),
1029 ConfigFileError::DuplicateId(_)
1030 ));
1031 }
1032
1033 #[test]
1034 fn test_get_with_empty_config() {
1035 let config = NetworksFileConfig::new(vec![]).unwrap();
1036
1037 let network_0 = config.get(0);
1038 assert!(network_0.is_none());
1039 }
1040
1041 #[test]
1042 fn test_get_and_first_equivalence() {
1043 let networks = vec![create_evm_network_wrapped("test-network")];
1044 let config = NetworksFileConfig::new(networks).unwrap();
1045
1046 let network_via_get = config.get(0);
1048 let network_via_first = config.first();
1049
1050 assert!(network_via_get.is_some());
1051 assert!(network_via_first.is_some());
1052 assert_eq!(
1053 network_via_get.unwrap().network_name(),
1054 network_via_first.unwrap().network_name()
1055 );
1056 assert_eq!(network_via_get.unwrap().network_name(), "test-network");
1057 }
1058
1059 #[test]
1060 #[allow(clippy::get_first)]
1061 fn test_different_access_methods() {
1062 let networks = vec![
1063 create_evm_network_wrapped("network-0"),
1064 create_solana_network_wrapped("network-1"),
1065 ];
1066 let config = NetworksFileConfig::new(networks).unwrap();
1067
1068 let net_0_get = config.get(0);
1070 assert!(net_0_get.is_some());
1071 assert_eq!(net_0_get.unwrap().network_name(), "network-0");
1072
1073 let net_0_first = config.first();
1075 assert!(net_0_first.is_some());
1076 assert_eq!(net_0_first.unwrap().network_name(), "network-0");
1077
1078 let net_0_index = &config[0];
1080 assert_eq!(net_0_index.network_name(), "network-0");
1081
1082 let net_0_direct = config.networks.get(0);
1084 assert!(net_0_direct.is_some());
1085 assert_eq!(net_0_direct.unwrap().network_name(), "network-0");
1086
1087 assert_eq!(
1089 net_0_get.unwrap().network_name(),
1090 net_0_first.unwrap().network_name()
1091 );
1092 assert_eq!(
1093 net_0_get.unwrap().network_name(),
1094 net_0_index.network_name()
1095 );
1096 assert_eq!(
1097 net_0_get.unwrap().network_name(),
1098 net_0_direct.unwrap().network_name()
1099 );
1100 }
1101
1102 #[test]
1103 fn test_first_with_non_empty_config() {
1104 let networks = vec![
1105 create_evm_network_wrapped("first-network"),
1106 create_solana_network_wrapped("second-network"),
1107 ];
1108 let config = NetworksFileConfig::new(networks).unwrap();
1109
1110 let first_network = config.first();
1111 assert!(first_network.is_some());
1112 assert_eq!(first_network.unwrap().network_name(), "first-network");
1113 }
1114
1115 #[test]
1116 fn test_first_with_empty_config() {
1117 let config = NetworksFileConfig::new(vec![]).unwrap();
1118
1119 let first_network = config.first();
1120 assert!(first_network.is_none());
1121 }
1122
1123 #[test]
1124 fn test_get_with_valid_index() {
1125 let networks = vec![
1126 create_evm_network_wrapped("network-0"),
1127 create_solana_network_wrapped("network-1"),
1128 create_stellar_network_wrapped("network-2"),
1129 ];
1130 let config = NetworksFileConfig::new(networks).unwrap();
1131
1132 let network_0 = config.get(0);
1133 assert!(network_0.is_some());
1134 assert_eq!(network_0.unwrap().network_name(), "network-0");
1135
1136 let network_1 = config.get(1);
1137 assert!(network_1.is_some());
1138 assert_eq!(network_1.unwrap().network_name(), "network-1");
1139
1140 let network_2 = config.get(2);
1141 assert!(network_2.is_some());
1142 assert_eq!(network_2.unwrap().network_name(), "network-2");
1143 }
1144
1145 #[test]
1146 fn test_get_with_invalid_index() {
1147 let networks = vec![create_evm_network_wrapped("only-network")];
1148 let config = NetworksFileConfig::new(networks).unwrap();
1149
1150 let network_out_of_bounds = config.get(1);
1151 assert!(network_out_of_bounds.is_none());
1152
1153 let network_large_index = config.get(100);
1154 assert!(network_large_index.is_none());
1155 }
1156
1157 #[test]
1158 fn test_networks_source_default() {
1159 let default_source = NetworksSource::default();
1160 match default_source {
1161 NetworksSource::Path(path) => {
1162 assert_eq!(path, "./config/networks");
1163 }
1164 _ => panic!("Default should be a Path variant"),
1165 }
1166 }
1167}