openzeppelin_relayer/repositories/network/
network_in_memory.rs1use crate::{
8 models::{NetworkRepoModel, NetworkType, RepositoryError},
9 repositories::{NetworkRepository, PaginatedResult, PaginationQuery, Repository},
10};
11use async_trait::async_trait;
12use eyre::Result;
13use std::collections::HashMap;
14use tokio::sync::{Mutex, MutexGuard};
15
16#[derive(Debug)]
17pub struct InMemoryNetworkRepository {
18 store: Mutex<HashMap<String, NetworkRepoModel>>,
19}
20
21impl Clone for InMemoryNetworkRepository {
22 fn clone(&self) -> Self {
23 let data = self
25 .store
26 .try_lock()
27 .map(|guard| guard.clone())
28 .unwrap_or_else(|_| HashMap::new());
29
30 Self {
31 store: Mutex::new(data),
32 }
33 }
34}
35
36impl InMemoryNetworkRepository {
37 pub fn new() -> Self {
38 Self {
39 store: Mutex::new(HashMap::new()),
40 }
41 }
42
43 async fn acquire_lock<T>(lock: &Mutex<T>) -> Result<MutexGuard<T>, RepositoryError> {
44 Ok(lock.lock().await)
45 }
46
47 pub async fn get(
49 &self,
50 network_type: NetworkType,
51 name: &str,
52 ) -> Result<Option<NetworkRepoModel>, RepositoryError> {
53 let store = Self::acquire_lock(&self.store).await?;
54 for (_, network) in store.iter() {
55 if network.network_type == network_type && network.name == name {
56 return Ok(Some(network.clone()));
57 }
58 }
59 Ok(None)
60 }
61}
62
63impl Default for InMemoryNetworkRepository {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69#[async_trait]
70impl Repository<NetworkRepoModel, String> for InMemoryNetworkRepository {
71 async fn create(&self, network: NetworkRepoModel) -> Result<NetworkRepoModel, RepositoryError> {
72 let mut store = Self::acquire_lock(&self.store).await?;
73 if store.contains_key(&network.id) {
74 return Err(RepositoryError::ConstraintViolation(format!(
75 "Network with ID {} already exists",
76 network.id
77 )));
78 }
79 store.insert(network.id.clone(), network.clone());
80 Ok(network)
81 }
82
83 async fn get_by_id(&self, id: String) -> Result<NetworkRepoModel, RepositoryError> {
84 let store = Self::acquire_lock(&self.store).await?;
85 match store.get(&id) {
86 Some(network) => Ok(network.clone()),
87 None => Err(RepositoryError::NotFound(format!(
88 "Network with ID {id} not found"
89 ))),
90 }
91 }
92
93 async fn update(
94 &self,
95 _id: String,
96 _network: NetworkRepoModel,
97 ) -> Result<NetworkRepoModel, RepositoryError> {
98 Err(RepositoryError::NotSupported("Not supported".to_string()))
99 }
100
101 async fn delete_by_id(&self, _id: String) -> Result<(), RepositoryError> {
102 Err(RepositoryError::NotSupported("Not supported".to_string()))
103 }
104
105 async fn list_all(&self) -> Result<Vec<NetworkRepoModel>, RepositoryError> {
106 let store = Self::acquire_lock(&self.store).await?;
107 let networks: Vec<NetworkRepoModel> = store.values().cloned().collect();
108 Ok(networks)
109 }
110
111 async fn list_paginated(
112 &self,
113 _query: PaginationQuery,
114 ) -> Result<PaginatedResult<NetworkRepoModel>, RepositoryError> {
115 Err(RepositoryError::NotSupported("Not supported".to_string()))
116 }
117
118 async fn count(&self) -> Result<usize, RepositoryError> {
119 let store = Self::acquire_lock(&self.store).await?;
120 Ok(store.len())
121 }
122
123 async fn has_entries(&self) -> Result<bool, RepositoryError> {
124 let store = Self::acquire_lock(&self.store).await?;
125 Ok(!store.is_empty())
126 }
127
128 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
129 let mut store = Self::acquire_lock(&self.store).await?;
130 store.clear();
131 Ok(())
132 }
133}
134
135#[async_trait]
136impl NetworkRepository for InMemoryNetworkRepository {
137 async fn get_by_name(
138 &self,
139 network_type: NetworkType,
140 name: &str,
141 ) -> Result<Option<NetworkRepoModel>, RepositoryError> {
142 self.get(network_type, name).await
143 }
144
145 async fn get_by_chain_id(
146 &self,
147 network_type: NetworkType,
148 chain_id: u64,
149 ) -> Result<Option<NetworkRepoModel>, RepositoryError> {
150 if network_type != NetworkType::Evm {
152 return Ok(None);
153 }
154
155 let store = Self::acquire_lock(&self.store).await?;
156 for (_, network) in store.iter() {
157 if network.network_type == network_type {
158 if let crate::models::NetworkConfigData::Evm(evm_config) = &network.config {
159 if evm_config.chain_id == Some(chain_id) {
160 return Ok(Some(network.clone()));
161 }
162 }
163 }
164 }
165 Ok(None)
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use crate::config::{
172 EvmNetworkConfig, NetworkConfigCommon, SolanaNetworkConfig, StellarNetworkConfig,
173 };
174
175 use super::*;
176
177 fn create_test_network(name: String, network_type: NetworkType) -> NetworkRepoModel {
178 let common = NetworkConfigCommon {
179 network: name.clone(),
180 from: None,
181 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
182 explorer_urls: None,
183 average_blocktime_ms: None,
184 is_testnet: Some(true),
185 tags: None,
186 };
187
188 match network_type {
189 NetworkType::Evm => {
190 let evm_config = EvmNetworkConfig {
191 common,
192 chain_id: Some(1),
193 required_confirmations: Some(1),
194 features: None,
195 symbol: Some("ETH".to_string()),
196 gas_price_cache: None,
197 };
198 NetworkRepoModel::new_evm(evm_config)
199 }
200 NetworkType::Solana => {
201 let solana_config = SolanaNetworkConfig { common };
202 NetworkRepoModel::new_solana(solana_config)
203 }
204 NetworkType::Stellar => {
205 let stellar_config = StellarNetworkConfig {
206 common,
207 passphrase: None,
208 };
209 NetworkRepoModel::new_stellar(stellar_config)
210 }
211 }
212 }
213
214 #[tokio::test]
215 async fn test_new_repository_is_empty() {
216 let repo = InMemoryNetworkRepository::new();
217 assert_eq!(repo.count().await.unwrap(), 0);
218 }
219
220 #[tokio::test]
221 async fn test_create_network() {
222 let repo = InMemoryNetworkRepository::new();
223 let network = create_test_network("mainnet".to_string(), NetworkType::Evm);
224
225 repo.create(network.clone()).await.unwrap();
226 assert_eq!(repo.count().await.unwrap(), 1);
227
228 let stored = repo.get_by_id(network.id.clone()).await.unwrap();
229 assert_eq!(stored.id, network.id);
230 assert_eq!(stored.name, network.name);
231 }
232
233 #[tokio::test]
234 async fn test_get_network_by_type_and_name() {
235 let repo = InMemoryNetworkRepository::new();
236 let network = create_test_network("mainnet".to_string(), NetworkType::Evm);
237
238 repo.create(network.clone()).await.unwrap();
239
240 let retrieved = repo.get(NetworkType::Evm, "mainnet").await.unwrap();
241 assert!(retrieved.is_some());
242 assert_eq!(retrieved.unwrap().name, "mainnet");
243 }
244
245 #[tokio::test]
246 async fn test_get_nonexistent_network() {
247 let repo = InMemoryNetworkRepository::new();
248
249 let result = repo.get(NetworkType::Evm, "nonexistent").await.unwrap();
250 assert!(result.is_none());
251 }
252
253 #[tokio::test]
254 async fn test_create_duplicate_network() {
255 let repo = InMemoryNetworkRepository::new();
256 let network = create_test_network("mainnet".to_string(), NetworkType::Evm);
257
258 repo.create(network.clone()).await.unwrap();
259 let result = repo.create(network).await;
260
261 assert!(matches!(
262 result,
263 Err(RepositoryError::ConstraintViolation(_))
264 ));
265 }
266
267 #[tokio::test]
268 async fn test_different_network_types_same_name() {
269 let repo = InMemoryNetworkRepository::new();
270 let evm_network = create_test_network("mainnet".to_string(), NetworkType::Evm);
271 let solana_network = create_test_network("mainnet".to_string(), NetworkType::Solana);
272
273 repo.create(evm_network.clone()).await.unwrap();
274 repo.create(solana_network.clone()).await.unwrap();
275
276 assert_eq!(repo.count().await.unwrap(), 2);
277
278 let evm_retrieved = repo.get(NetworkType::Evm, "mainnet").await.unwrap();
279 let solana_retrieved = repo.get(NetworkType::Solana, "mainnet").await.unwrap();
280
281 assert!(evm_retrieved.is_some());
282 assert!(solana_retrieved.is_some());
283 assert_eq!(evm_retrieved.unwrap().network_type, NetworkType::Evm);
284 assert_eq!(solana_retrieved.unwrap().network_type, NetworkType::Solana);
285 }
286
287 #[tokio::test]
288 async fn test_unsupported_operations() {
289 let repo = InMemoryNetworkRepository::new();
290 let network = create_test_network("test".to_string(), NetworkType::Evm);
291
292 let update_result = repo.update("test".to_string(), network.clone()).await;
293 assert!(matches!(
294 update_result,
295 Err(RepositoryError::NotSupported(_))
296 ));
297
298 let delete_result = repo.delete_by_id("test".to_string()).await;
299 assert!(matches!(
300 delete_result,
301 Err(RepositoryError::NotSupported(_))
302 ));
303
304 let pagination_result = repo
305 .list_paginated(PaginationQuery {
306 page: 1,
307 per_page: 10,
308 })
309 .await;
310 assert!(matches!(
311 pagination_result,
312 Err(RepositoryError::NotSupported(_))
313 ));
314 }
315
316 #[tokio::test]
317 async fn test_has_entries() {
318 let repo = InMemoryNetworkRepository::new();
319 assert!(!repo.has_entries().await.unwrap());
320
321 let network = create_test_network("test".to_string(), NetworkType::Evm);
322
323 repo.create(network.clone()).await.unwrap();
324 assert!(repo.has_entries().await.unwrap());
325 }
326
327 #[tokio::test]
328 async fn test_drop_all_entries() {
329 let repo = InMemoryNetworkRepository::new();
330 let network = create_test_network("test".to_string(), NetworkType::Evm);
331
332 repo.create(network.clone()).await.unwrap();
333 assert!(repo.has_entries().await.unwrap());
334
335 repo.drop_all_entries().await.unwrap();
336 assert!(!repo.has_entries().await.unwrap());
337 }
338}