openzeppelin_monitor/repositories/
network.rs

1//! Network configuration repository implementation.
2//!
3//! This module provides storage and retrieval of network configurations, which define
4//! blockchain connection details and parameters. The repository loads network
5//! configurations from JSON files.
6
7#![allow(clippy::result_large_err)]
8
9use std::{collections::HashMap, path::Path};
10
11use async_trait::async_trait;
12
13use crate::{
14	models::{ConfigLoader, Network},
15	repositories::error::RepositoryError,
16};
17
18/// Repository for storing and retrieving network configurations
19#[derive(Clone)]
20pub struct NetworkRepository {
21	/// Map of network slugs to their configurations
22	pub networks: HashMap<String, Network>,
23}
24
25impl NetworkRepository {
26	/// Create a new network repository from the given path
27	///
28	/// Loads all network configurations from JSON files in the specified directory
29	/// (or default config directory if None is provided).
30	pub async fn new(path: Option<&Path>) -> Result<Self, RepositoryError> {
31		let networks = Self::load_all(path).await?;
32		Ok(NetworkRepository { networks })
33	}
34}
35
36/// Interface for network repository implementations
37///
38/// This trait defines the standard operations that any network repository must support,
39/// allowing for different storage backends while maintaining a consistent interface.
40#[async_trait]
41pub trait NetworkRepositoryTrait: Clone {
42	/// Create a new repository instance
43	async fn new(path: Option<&Path>) -> Result<Self, RepositoryError>
44	where
45		Self: Sized;
46
47	/// Load all network configurations from the given path
48	///
49	/// If no path is provided, uses the default config directory.
50	/// This is a static method that doesn't require an instance.
51	async fn load_all(path: Option<&Path>) -> Result<HashMap<String, Network>, RepositoryError>;
52
53	/// Get a specific network by ID
54	///
55	/// Returns None if the network doesn't exist.
56	fn get(&self, network_id: &str) -> Option<Network>;
57
58	/// Get all networks
59	///
60	/// Returns a copy of the network map to prevent external mutation.
61	fn get_all(&self) -> HashMap<String, Network>;
62}
63
64#[async_trait]
65impl NetworkRepositoryTrait for NetworkRepository {
66	async fn new(path: Option<&Path>) -> Result<Self, RepositoryError> {
67		NetworkRepository::new(path).await
68	}
69
70	async fn load_all(path: Option<&Path>) -> Result<HashMap<String, Network>, RepositoryError> {
71		Network::load_all(path).await.map_err(|e| {
72			RepositoryError::load_error(
73				"Failed to load networks",
74				Some(Box::new(e)),
75				Some(HashMap::from([(
76					"path".to_string(),
77					path.map_or_else(|| "default".to_string(), |p| p.display().to_string()),
78				)])),
79			)
80		})
81	}
82
83	fn get(&self, network_id: &str) -> Option<Network> {
84		self.networks.get(network_id).cloned()
85	}
86
87	fn get_all(&self) -> HashMap<String, Network> {
88		self.networks.clone()
89	}
90}
91
92/// Service layer for network repository operations
93///
94/// This type provides a higher-level interface for working with network configurations,
95/// handling repository initialization and access through a trait-based interface.
96
97#[derive(Clone)]
98pub struct NetworkService<T: NetworkRepositoryTrait> {
99	repository: T,
100}
101
102impl<T: NetworkRepositoryTrait> NetworkService<T> {
103	/// Create a new network service with the default repository implementation
104	pub async fn new(
105		path: Option<&Path>,
106	) -> Result<NetworkService<NetworkRepository>, RepositoryError> {
107		let repository = NetworkRepository::new(path).await?;
108		Ok(NetworkService { repository })
109	}
110
111	/// Create a new network service with a custom repository implementation
112	pub fn new_with_repository(repository: T) -> Result<Self, RepositoryError> {
113		Ok(NetworkService { repository })
114	}
115
116	/// Create a new network service with a specific configuration path
117	pub async fn new_with_path(
118		path: Option<&Path>,
119	) -> Result<NetworkService<NetworkRepository>, RepositoryError> {
120		let repository = NetworkRepository::new(path).await?;
121		Ok(NetworkService { repository })
122	}
123
124	/// Get a specific network by ID
125	pub fn get(&self, network_id: &str) -> Option<Network> {
126		self.repository.get(network_id)
127	}
128
129	/// Get all networks
130	pub fn get_all(&self) -> HashMap<String, Network> {
131		self.repository.get_all()
132	}
133}
134
135#[cfg(test)]
136mod tests {
137	use super::*;
138
139	#[tokio::test]
140	async fn test_load_error_messages() {
141		// Test with invalid path to trigger load error
142		let invalid_path = Path::new("/non/existent/path");
143		let result = NetworkRepository::load_all(Some(invalid_path)).await;
144
145		assert!(result.is_err());
146		let err = result.unwrap_err();
147		match err {
148			RepositoryError::LoadError(message) => {
149				assert!(message.to_string().contains("Failed to load networks"));
150			}
151			_ => panic!("Expected RepositoryError::LoadError"),
152		}
153	}
154}