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