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 {} not found",
89 id
90 ))),
91 }
92 }
93
94 async fn update(
95 &self,
96 _id: String,
97 _network: NetworkRepoModel,
98 ) -> Result<NetworkRepoModel, RepositoryError> {
99 Err(RepositoryError::NotSupported("Not supported".to_string()))
100 }
101
102 async fn delete_by_id(&self, _id: String) -> Result<(), RepositoryError> {
103 Err(RepositoryError::NotSupported("Not supported".to_string()))
104 }
105
106 async fn list_all(&self) -> Result<Vec<NetworkRepoModel>, RepositoryError> {
107 let store = Self::acquire_lock(&self.store).await?;
108 let networks: Vec<NetworkRepoModel> = store.values().cloned().collect();
109 Ok(networks)
110 }
111
112 async fn list_paginated(
113 &self,
114 _query: PaginationQuery,
115 ) -> Result<PaginatedResult<NetworkRepoModel>, RepositoryError> {
116 Err(RepositoryError::NotSupported("Not supported".to_string()))
117 }
118
119 async fn count(&self) -> Result<usize, RepositoryError> {
120 let store = Self::acquire_lock(&self.store).await?;
121 Ok(store.len())
122 }
123
124 async fn has_entries(&self) -> Result<bool, RepositoryError> {
125 let store = Self::acquire_lock(&self.store).await?;
126 Ok(!store.is_empty())
127 }
128
129 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
130 let mut store = Self::acquire_lock(&self.store).await?;
131 store.clear();
132 Ok(())
133 }
134}
135
136#[async_trait]
137impl NetworkRepository for InMemoryNetworkRepository {
138 async fn get_by_name(
139 &self,
140 network_type: NetworkType,
141 name: &str,
142 ) -> Result<Option<NetworkRepoModel>, RepositoryError> {
143 self.get(network_type, name).await
144 }
145
146 async fn get_by_chain_id(
147 &self,
148 network_type: NetworkType,
149 chain_id: u64,
150 ) -> Result<Option<NetworkRepoModel>, RepositoryError> {
151 if network_type != NetworkType::Evm {
153 return Ok(None);
154 }
155
156 let store = Self::acquire_lock(&self.store).await?;
157 for (_, network) in store.iter() {
158 if network.network_type == network_type {
159 if let crate::models::NetworkConfigData::Evm(evm_config) = &network.config {
160 if evm_config.chain_id == Some(chain_id) {
161 return Ok(Some(network.clone()));
162 }
163 }
164 }
165 }
166 Ok(None)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use crate::config::{
173 EvmNetworkConfig, NetworkConfigCommon, SolanaNetworkConfig, StellarNetworkConfig,
174 };
175
176 use super::*;
177
178 fn create_test_network(name: String, network_type: NetworkType) -> NetworkRepoModel {
179 let common = NetworkConfigCommon {
180 network: name.clone(),
181 from: None,
182 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
183 explorer_urls: None,
184 average_blocktime_ms: None,
185 is_testnet: Some(true),
186 tags: None,
187 };
188
189 match network_type {
190 NetworkType::Evm => {
191 let evm_config = EvmNetworkConfig {
192 common,
193 chain_id: Some(1),
194 required_confirmations: Some(1),
195 features: None,
196 symbol: Some("ETH".to_string()),
197 gas_price_cache: None,
198 };
199 NetworkRepoModel::new_evm(evm_config)
200 }
201 NetworkType::Solana => {
202 let solana_config = SolanaNetworkConfig { common };
203 NetworkRepoModel::new_solana(solana_config)
204 }
205 NetworkType::Stellar => {
206 let stellar_config = StellarNetworkConfig {
207 common,
208 passphrase: None,
209 };
210 NetworkRepoModel::new_stellar(stellar_config)
211 }
212 }
213 }
214
215 #[tokio::test]
216 async fn test_new_repository_is_empty() {
217 let repo = InMemoryNetworkRepository::new();
218 assert_eq!(repo.count().await.unwrap(), 0);
219 }
220
221 #[tokio::test]
222 async fn test_create_network() {
223 let repo = InMemoryNetworkRepository::new();
224 let network = create_test_network("mainnet".to_string(), NetworkType::Evm);
225
226 repo.create(network.clone()).await.unwrap();
227 assert_eq!(repo.count().await.unwrap(), 1);
228
229 let stored = repo.get_by_id(network.id.clone()).await.unwrap();
230 assert_eq!(stored.id, network.id);
231 assert_eq!(stored.name, network.name);
232 }
233
234 #[tokio::test]
235 async fn test_get_network_by_type_and_name() {
236 let repo = InMemoryNetworkRepository::new();
237 let network = create_test_network("mainnet".to_string(), NetworkType::Evm);
238
239 repo.create(network.clone()).await.unwrap();
240
241 let retrieved = repo.get(NetworkType::Evm, "mainnet").await.unwrap();
242 assert!(retrieved.is_some());
243 assert_eq!(retrieved.unwrap().name, "mainnet");
244 }
245
246 #[tokio::test]
247 async fn test_get_nonexistent_network() {
248 let repo = InMemoryNetworkRepository::new();
249
250 let result = repo.get(NetworkType::Evm, "nonexistent").await.unwrap();
251 assert!(result.is_none());
252 }
253
254 #[tokio::test]
255 async fn test_create_duplicate_network() {
256 let repo = InMemoryNetworkRepository::new();
257 let network = create_test_network("mainnet".to_string(), NetworkType::Evm);
258
259 repo.create(network.clone()).await.unwrap();
260 let result = repo.create(network).await;
261
262 assert!(matches!(
263 result,
264 Err(RepositoryError::ConstraintViolation(_))
265 ));
266 }
267
268 #[tokio::test]
269 async fn test_different_network_types_same_name() {
270 let repo = InMemoryNetworkRepository::new();
271 let evm_network = create_test_network("mainnet".to_string(), NetworkType::Evm);
272 let solana_network = create_test_network("mainnet".to_string(), NetworkType::Solana);
273
274 repo.create(evm_network.clone()).await.unwrap();
275 repo.create(solana_network.clone()).await.unwrap();
276
277 assert_eq!(repo.count().await.unwrap(), 2);
278
279 let evm_retrieved = repo.get(NetworkType::Evm, "mainnet").await.unwrap();
280 let solana_retrieved = repo.get(NetworkType::Solana, "mainnet").await.unwrap();
281
282 assert!(evm_retrieved.is_some());
283 assert!(solana_retrieved.is_some());
284 assert_eq!(evm_retrieved.unwrap().network_type, NetworkType::Evm);
285 assert_eq!(solana_retrieved.unwrap().network_type, NetworkType::Solana);
286 }
287
288 #[tokio::test]
289 async fn test_unsupported_operations() {
290 let repo = InMemoryNetworkRepository::new();
291 let network = create_test_network("test".to_string(), NetworkType::Evm);
292
293 let update_result = repo.update("test".to_string(), network.clone()).await;
294 assert!(matches!(
295 update_result,
296 Err(RepositoryError::NotSupported(_))
297 ));
298
299 let delete_result = repo.delete_by_id("test".to_string()).await;
300 assert!(matches!(
301 delete_result,
302 Err(RepositoryError::NotSupported(_))
303 ));
304
305 let pagination_result = repo
306 .list_paginated(PaginationQuery {
307 page: 1,
308 per_page: 10,
309 })
310 .await;
311 assert!(matches!(
312 pagination_result,
313 Err(RepositoryError::NotSupported(_))
314 ));
315 }
316
317 #[tokio::test]
318 async fn test_has_entries() {
319 let repo = InMemoryNetworkRepository::new();
320 assert!(!repo.has_entries().await.unwrap());
321
322 let network = create_test_network("test".to_string(), NetworkType::Evm);
323
324 repo.create(network.clone()).await.unwrap();
325 assert!(repo.has_entries().await.unwrap());
326 }
327
328 #[tokio::test]
329 async fn test_drop_all_entries() {
330 let repo = InMemoryNetworkRepository::new();
331 let network = create_test_network("test".to_string(), NetworkType::Evm);
332
333 repo.create(network.clone()).await.unwrap();
334 assert!(repo.has_entries().await.unwrap());
335
336 repo.drop_all_entries().await.unwrap();
337 assert!(!repo.has_entries().await.unwrap());
338 }
339}