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_with_inheritance_resolution() {
833 let json = r#"[
834 {
835 "type": "evm",
836 "network": "parent",
837 "chain_id": 31337,
838 "required_confirmations": 1,
839 "symbol": "ETH",
840 "rpc_urls": ["https://rpc.parent.example.com"]
841 },
842 {
843 "type": "evm",
844 "network": "child",
845 "from": "parent",
846 "chain_id": 31338,
847 "required_confirmations": 1,
848 "symbol": "ETH"
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 child = config
859 .get_network(ConfigFileNetworkType::Evm, "child")
860 .unwrap();
861 assert_eq!(child.inherits_from(), Some("parent")); if let NetworkFileConfig::Evm(child_evm) = child {
865 assert!(child_evm.common.rpc_urls.is_some()); assert_eq!(child_evm.chain_id, Some(31338)); }
868 }
869
870 #[test]
871 fn test_deserialize_with_invalid_inheritance() {
872 let json = r#"[
873 {
874 "type": "evm",
875 "network": "child",
876 "from": "non-existent-parent",
877 "chain_id": 31337,
878 "required_confirmations": 1,
879 "symbol": "ETH",
880 "rpc_urls": ["https://rpc.test.example.com"]
881 }
882 ]"#;
883
884 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
885 assert!(result.is_err());
886 }
887
888 #[test]
890 fn test_large_number_of_networks() {
891 let mut networks = Vec::new();
892 for i in 0..100 {
893 networks.push(create_evm_network_wrapped(&format!("network-{}", i)));
894 }
895
896 let config = NetworksFileConfig::new(networks);
897 assert!(config.is_ok());
898 let config = config.unwrap();
899 assert_eq!(config.len(), 100);
900
901 for i in 0..100 {
903 assert!(config
904 .get_network(ConfigFileNetworkType::Evm, &format!("network-{}", i))
905 .is_some());
906 }
907 }
908
909 #[test]
910 fn test_unicode_network_names() {
911 let networks = vec![
912 create_evm_network_wrapped("测试网络"),
913 create_solana_network_wrapped("тестовая-сеть"),
914 create_stellar_network_wrapped("réseau-test"),
915 ];
916
917 let config = NetworksFileConfig::new(networks);
918 assert!(config.is_ok());
919 let config = config.unwrap();
920 assert_eq!(config.len(), 3);
921 assert!(config
922 .get_network(ConfigFileNetworkType::Evm, "测试网络")
923 .is_some());
924 assert!(config
925 .get_network(ConfigFileNetworkType::Solana, "тестовая-сеть")
926 .is_some());
927 assert!(config
928 .get_network(ConfigFileNetworkType::Stellar, "réseau-test")
929 .is_some());
930 }
931
932 #[test]
933 fn test_special_characters_in_network_names() {
934 let networks = vec![
935 create_evm_network_wrapped("test-network_123"),
936 create_solana_network_wrapped("test.network.with.dots"),
937 create_stellar_network_wrapped("test@network#with$symbols"),
938 ];
939
940 let config = NetworksFileConfig::new(networks);
941 assert!(config.is_ok());
942 let config = config.unwrap();
943 assert_eq!(config.len(), 3);
944 }
945
946 #[test]
947 fn test_very_long_network_names() {
948 let long_name = "a".repeat(1000);
949 let networks = vec![create_evm_network_wrapped(&long_name)];
950
951 let config = NetworksFileConfig::new(networks);
952 assert!(config.is_ok());
953 let config = config.unwrap();
954 assert!(config
955 .get_network(ConfigFileNetworkType::Evm, &long_name)
956 .is_some());
957 }
958
959 #[test]
960 fn test_complex_inheritance_scenario() {
961 let networks = vec![
962 create_evm_network_wrapped("evm-root"),
964 create_solana_network_wrapped("solana-root"),
965 create_evm_network_wrapped_with_parent("evm-child1", "evm-root"),
967 create_evm_network_wrapped_with_parent("evm-child2", "evm-root"),
968 create_solana_network_wrapped_with_parent("solana-child1", "solana-root"),
969 create_evm_network_wrapped_with_parent("evm-grandchild", "evm-child1"),
971 ];
972
973 let config = NetworksFileConfig::new(networks);
974 assert!(config.is_ok());
975 let config = config.unwrap();
976 assert_eq!(config.len(), 6);
977
978 let flattened = config.flatten();
979 assert!(flattened.is_ok());
980 let flattened = flattened.unwrap();
981 assert_eq!(flattened.len(), 6);
982 }
983
984 #[test]
985 fn test_new_with_duplicate_network_names_across_types() {
986 let networks = vec![
988 create_evm_network_wrapped("mainnet"),
989 create_solana_network_wrapped("mainnet"),
990 create_stellar_network_wrapped("mainnet"),
991 ];
992 let result = NetworksFileConfig::new(networks);
993
994 assert!(result.is_ok());
995 let config = result.unwrap();
996 assert_eq!(config.networks.len(), 3);
997 assert_eq!(config.network_map.len(), 3);
998
999 assert!(config
1001 .get_network(ConfigFileNetworkType::Evm, "mainnet")
1002 .is_some());
1003 assert!(config
1004 .get_network(ConfigFileNetworkType::Solana, "mainnet")
1005 .is_some());
1006 assert!(config
1007 .get_network(ConfigFileNetworkType::Stellar, "mainnet")
1008 .is_some());
1009 }
1010
1011 #[test]
1012 fn test_new_with_duplicate_network_names_within_same_type() {
1013 let networks = vec![
1014 create_evm_network_wrapped("duplicate-evm"),
1015 create_evm_network_wrapped("duplicate-evm"),
1016 ];
1017 let result = NetworksFileConfig::new(networks);
1018
1019 assert!(result.is_err());
1020 assert!(matches!(
1021 result.unwrap_err(),
1022 ConfigFileError::DuplicateId(_)
1023 ));
1024 }
1025
1026 #[test]
1027 fn test_get_with_empty_config() {
1028 let config = NetworksFileConfig::new(vec![]).unwrap();
1029
1030 let network_0 = config.get(0);
1031 assert!(network_0.is_none());
1032 }
1033
1034 #[test]
1035 fn test_get_and_first_equivalence() {
1036 let networks = vec![create_evm_network_wrapped("test-network")];
1037 let config = NetworksFileConfig::new(networks).unwrap();
1038
1039 let network_via_get = config.get(0);
1041 let network_via_first = config.first();
1042
1043 assert!(network_via_get.is_some());
1044 assert!(network_via_first.is_some());
1045 assert_eq!(
1046 network_via_get.unwrap().network_name(),
1047 network_via_first.unwrap().network_name()
1048 );
1049 assert_eq!(network_via_get.unwrap().network_name(), "test-network");
1050 }
1051
1052 #[test]
1053 #[allow(clippy::get_first)]
1054 fn test_different_access_methods() {
1055 let networks = vec![
1056 create_evm_network_wrapped("network-0"),
1057 create_solana_network_wrapped("network-1"),
1058 ];
1059 let config = NetworksFileConfig::new(networks).unwrap();
1060
1061 let net_0_get = config.get(0);
1063 assert!(net_0_get.is_some());
1064 assert_eq!(net_0_get.unwrap().network_name(), "network-0");
1065
1066 let net_0_first = config.first();
1068 assert!(net_0_first.is_some());
1069 assert_eq!(net_0_first.unwrap().network_name(), "network-0");
1070
1071 let net_0_index = &config[0];
1073 assert_eq!(net_0_index.network_name(), "network-0");
1074
1075 let net_0_direct = config.networks.get(0);
1077 assert!(net_0_direct.is_some());
1078 assert_eq!(net_0_direct.unwrap().network_name(), "network-0");
1079
1080 assert_eq!(
1082 net_0_get.unwrap().network_name(),
1083 net_0_first.unwrap().network_name()
1084 );
1085 assert_eq!(
1086 net_0_get.unwrap().network_name(),
1087 net_0_index.network_name()
1088 );
1089 assert_eq!(
1090 net_0_get.unwrap().network_name(),
1091 net_0_direct.unwrap().network_name()
1092 );
1093 }
1094
1095 #[test]
1096 fn test_first_with_non_empty_config() {
1097 let networks = vec![
1098 create_evm_network_wrapped("first-network"),
1099 create_solana_network_wrapped("second-network"),
1100 ];
1101 let config = NetworksFileConfig::new(networks).unwrap();
1102
1103 let first_network = config.first();
1104 assert!(first_network.is_some());
1105 assert_eq!(first_network.unwrap().network_name(), "first-network");
1106 }
1107
1108 #[test]
1109 fn test_first_with_empty_config() {
1110 let config = NetworksFileConfig::new(vec![]).unwrap();
1111
1112 let first_network = config.first();
1113 assert!(first_network.is_none());
1114 }
1115
1116 #[test]
1117 fn test_get_with_valid_index() {
1118 let networks = vec![
1119 create_evm_network_wrapped("network-0"),
1120 create_solana_network_wrapped("network-1"),
1121 create_stellar_network_wrapped("network-2"),
1122 ];
1123 let config = NetworksFileConfig::new(networks).unwrap();
1124
1125 let network_0 = config.get(0);
1126 assert!(network_0.is_some());
1127 assert_eq!(network_0.unwrap().network_name(), "network-0");
1128
1129 let network_1 = config.get(1);
1130 assert!(network_1.is_some());
1131 assert_eq!(network_1.unwrap().network_name(), "network-1");
1132
1133 let network_2 = config.get(2);
1134 assert!(network_2.is_some());
1135 assert_eq!(network_2.unwrap().network_name(), "network-2");
1136 }
1137
1138 #[test]
1139 fn test_get_with_invalid_index() {
1140 let networks = vec![create_evm_network_wrapped("only-network")];
1141 let config = NetworksFileConfig::new(networks).unwrap();
1142
1143 let network_out_of_bounds = config.get(1);
1144 assert!(network_out_of_bounds.is_none());
1145
1146 let network_large_index = config.get(100);
1147 assert!(network_large_index.is_none());
1148 }
1149
1150 #[test]
1151 fn test_networks_source_default() {
1152 let default_source = NetworksSource::default();
1153 match default_source {
1154 NetworksSource::Path(path) => {
1155 assert_eq!(path, "./config/networks");
1156 }
1157 _ => panic!("Default should be a Path variant"),
1158 }
1159 }
1160}