openzeppelin_relayer/config/config_file/network/
collection.rs

1//! Network Configuration Collection Management
2//!
3//! This module provides collection management for multiple network configurations with
4//! inheritance resolution, validation, and flexible loading from JSON arrays or directories.
5//!
6//! ## Core Features
7//!
8//! - **Multi-network support**: Manages EVM, Solana, and Stellar networks in a single collection
9//! - **Inheritance resolution**: Resolves complex inheritance hierarchies with type safety
10//! - **Flexible loading**: Supports JSON arrays and directory-based configuration sources
11//! - **Validation**: Comprehensive validation with detailed error reporting
12
13use 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/// Represents the complete configuration for all defined networks.
22///
23/// This structure holds configurations loaded from a file or a directory of files
24/// and provides methods to validate and process them, including resolving inheritance.
25#[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
32/// Custom deserialization logic for `NetworksFileConfig`.
33///
34/// This allows `NetworksFileConfig` to be created from either a direct list of network
35/// configurations, a path string pointing to a directory of configuration files, or null/missing
36/// for the default path ("./config/networks").
37impl<'de> Deserialize<'de> for NetworksFileConfig {
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: Deserializer<'de>,
41    {
42        // Use Option to handle missing fields gracefully
43        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        // Check if networks is empty and return error
50        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        // First, create an instance with unflattened networks.
57        // This will perform initial validations like duplicate name checks.
58        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        // Now, flatten the configuration. (Resolve inheritance)
66        unflattened_config
67            .flatten()
68            .map_err(|e| de::Error::custom(format!("Error flattening NetworksFileConfig: {:?}", e)))
69    }
70}
71
72impl NetworksFileConfig {
73    /// Creates a new `NetworksFileConfig` instance from a vector of network configurations.
74    ///
75    /// # Returns
76    /// - `Ok(Self)` if all network names are unique within their respective types and the instance is successfully created.
77    /// - `Err(ConfigFileError)` if duplicate network names are found within the same network type.
78    pub fn new(networks: Vec<NetworkFileConfig>) -> Result<Self, ConfigFileError> {
79        let mut network_map = HashMap::new();
80
81        // Build the network map for efficient lookups
82        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 an error if we find a duplicate within the same network type
89                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        // Check inheritance references and types
102        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    /// Retrieves a network configuration by its network type and name.
112    ///
113    /// # Arguments
114    /// * `network_type` - The type of the network to retrieve.
115    /// * `name` - The name of the network to retrieve.
116    ///
117    /// # Returns
118    /// - `Some(&NetworkFileConfig)` if a network with the given type and name exists.
119    /// - `None` if no network with the given type and name is found.
120    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    /// Builds a new set of networks with all inheritance chains resolved and flattened.
132    ///
133    /// This method processes all networks and their inheritance relationships to produce
134    /// a set of fully expanded network configurations where each network includes all properties
135    /// from its parent networks, with any overrides applied.
136    ///
137    /// # Returns
138    /// - `Result<NetworksFileConfig, ConfigFileError>` containing either the flattened configuration
139    ///   or an error if any inheritance issues are encountered.
140    pub fn flatten(&self) -> Result<NetworksFileConfig, ConfigFileError> {
141        // Process each network to resolve inheritance
142        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    /// Creates a fully resolved network configuration by merging properties from its inheritance chain.
152    ///
153    /// # Arguments
154    /// * `network` - A reference to the `NetworkFileConfig` to resolve.
155    ///
156    /// # Returns
157    /// - `Ok(NetworkFileConfig)` containing the fully resolved network configuration.
158    /// - `Err(ConfigFileError)` if any issues are encountered during inheritance resolution.
159    fn resolve_inheritance(
160        &self,
161        network: &NetworkFileConfig,
162    ) -> Result<NetworkFileConfig, ConfigFileError> {
163        // If no inheritance, return a clone of the original
164        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        // Create the inheritance resolver with a lookup function that uses network type
173        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    /// Validates the entire networks configuration structure.
196    ///
197    /// # Returns
198    /// - `Ok(())` if the entire configuration is valid.
199    /// - `Err(ConfigFileError)` if any validation fails (duplicate names, invalid inheritance,
200    ///   incompatible inheritance types, or errors from individual network validations).
201    pub fn validate(&self) -> Result<(), ConfigFileError> {
202        for network in &self.networks {
203            network.validate()?;
204        }
205        Ok(())
206    }
207
208    /// Traces the inheritance path for a given network to check for cycles or invalid references.
209    ///
210    /// # Arguments
211    /// - `start_network_name` - The name of the network to trace inheritance for.
212    /// - `network_type` - The type of the network to trace inheritance for.
213    ///
214    /// # Returns
215    /// - `Ok(())` if the inheritance chain is valid.
216    /// - `Err(ConfigFileError)` if a cycle or invalid reference is detected.
217    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            // Check cycle first
227            if current_path_names.contains(&current_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    /// Returns an iterator over all networks.
282    pub fn iter(&self) -> impl Iterator<Item = &NetworkFileConfig> {
283        self.networks.iter()
284    }
285
286    /// Returns the number of networks in the configuration.
287    pub fn len(&self) -> usize {
288        self.networks.len()
289    }
290
291    /// Returns true if there are no networks in the configuration.
292    pub fn is_empty(&self) -> bool {
293        self.networks.is_empty()
294    }
295
296    /// Filters networks by type.
297    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    /// Gets all network names.
307    pub fn network_names(&self) -> impl Iterator<Item = &str> {
308        self.networks.iter().map(|network| network.network_name())
309    }
310
311    /// Returns the first network in the configuration.
312    ///
313    /// # Returns
314    /// - `Some(&NetworkFileConfig)` if there is at least one network.
315    /// - `None` if the configuration is empty.
316    pub fn first(&self) -> Option<&NetworkFileConfig> {
317        self.networks.first()
318    }
319
320    /// Returns a reference to the network at the given index.
321    ///
322    /// # Arguments
323    /// * `index` - The index of the network to retrieve.
324    ///
325    /// # Returns
326    /// - `Some(&NetworkFileConfig)` if a network exists at the given index.
327    /// - `None` if the index is out of bounds.
328    pub fn get(&self, index: usize) -> Option<&NetworkFileConfig> {
329        self.networks.get(index)
330    }
331}
332
333// Implementation of Index trait for array-like access (config[0])
334impl 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()); // Empty config is valid for validation
570    }
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        // Child should still exist with inheritance information preserved
600        let child = flattened.get_network(ConfigFileNetworkType::Evm, "child");
601        assert!(child.is_some());
602        // The from field is preserved to show inheritance source, but inheritance is resolved
603        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    // Tests for Default implementation
763    #[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        // Create test network files
810        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        // After deserialization, inheritance should be resolved but from field preserved
867        let child = config
868            .get_network(ConfigFileNetworkType::Evm, "child")
869            .unwrap();
870        assert_eq!(child.inherits_from(), Some("parent")); // From field preserved
871
872        // Verify that child has inherited properties from parent
873        if let NetworkFileConfig::Evm(child_evm) = child {
874            assert!(child_evm.common.rpc_urls.is_some()); // Should have inherited RPC URLs
875            assert_eq!(child_evm.chain_id, Some(31338)); // Should have overridden chain_id
876        }
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    // Edge cases and stress tests
898    #[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        // Test that all networks are accessible
911        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            // Root networks
972            create_evm_network_wrapped("evm-root"),
973            create_solana_network_wrapped("solana-root"),
974            // First level children
975            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            // Second level children
979            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        // Should allow same name across different network types
996        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        // Verify we can retrieve each network by type and name
1009        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        // Both methods should return the same result
1049        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        // Method 1: Using .get())
1071        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        // Method 2: Using .first()
1076        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        // Method 3: Using indexing [0] (Index trait)
1081        let net_0_index = &config[0];
1082        assert_eq!(net_0_index.network_name(), "network-0");
1083
1084        // Method 4: Using direct field access
1085        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        // All should reference the same network
1090        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}