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 };
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}