openzeppelin_relayer/repositories/network/
mod.rs

1//! Network Repository Module
2//!
3//! This module provides the network repository layer for the OpenZeppelin Relayer service.
4//! It implements the Repository pattern to abstract network configuration persistence operations,
5//! supporting both in-memory and Redis-backed storage implementations.
6//!
7//! ## Features
8//!
9//! - **CRUD Operations**: Create, read, update, and delete network configurations
10//! - **Multi-Chain Support**: Handle EVM, Solana, and Stellar network configurations
11//! - **Specialized Queries**: Find networks by name and chain ID
12//! - **Pagination Support**: Efficient paginated listing of networks
13//! - **Type Safety**: Strongly typed network configurations per blockchain type
14//!
15//! ## Repository Implementations
16//!
17//! - [`InMemoryNetworkRepository`]: Fast in-memory storage for testing/development
18//! - [`RedisNetworkRepository`]: Redis-backed storage for production environments
19//!
20
21use async_trait::async_trait;
22use redis::aio::ConnectionManager;
23use std::sync::Arc;
24
25mod network_in_memory;
26mod network_redis;
27
28pub use network_in_memory::InMemoryNetworkRepository;
29pub use network_redis::RedisNetworkRepository;
30
31use crate::models::{NetworkRepoModel, NetworkType, RepositoryError};
32use crate::repositories::{PaginatedResult, PaginationQuery, Repository};
33
34#[async_trait]
35pub trait NetworkRepository: Repository<NetworkRepoModel, String> {
36    /// Get a network by network type and name
37    async fn get_by_name(
38        &self,
39        network_type: NetworkType,
40        name: &str,
41    ) -> Result<Option<NetworkRepoModel>, RepositoryError>;
42
43    /// Get a network by network type and chain ID
44    async fn get_by_chain_id(
45        &self,
46        network_type: NetworkType,
47        chain_id: u64,
48    ) -> Result<Option<NetworkRepoModel>, RepositoryError>;
49}
50
51#[derive(Debug, Clone)]
52pub enum NetworkRepositoryStorage {
53    InMemory(InMemoryNetworkRepository),
54    Redis(RedisNetworkRepository),
55}
56
57impl NetworkRepositoryStorage {
58    pub fn new_in_memory() -> Self {
59        Self::InMemory(InMemoryNetworkRepository::new())
60    }
61
62    pub fn new_redis(
63        connection_manager: Arc<ConnectionManager>,
64        key_prefix: String,
65    ) -> Result<Self, RepositoryError> {
66        let redis_repo = RedisNetworkRepository::new(connection_manager, key_prefix)?;
67        Ok(Self::Redis(redis_repo))
68    }
69}
70
71#[async_trait]
72impl Repository<NetworkRepoModel, String> for NetworkRepositoryStorage {
73    async fn create(&self, entity: NetworkRepoModel) -> Result<NetworkRepoModel, RepositoryError> {
74        match self {
75            NetworkRepositoryStorage::InMemory(repo) => repo.create(entity).await,
76            NetworkRepositoryStorage::Redis(repo) => repo.create(entity).await,
77        }
78    }
79
80    async fn get_by_id(&self, id: String) -> Result<NetworkRepoModel, RepositoryError> {
81        match self {
82            NetworkRepositoryStorage::InMemory(repo) => repo.get_by_id(id).await,
83            NetworkRepositoryStorage::Redis(repo) => repo.get_by_id(id).await,
84        }
85    }
86
87    async fn list_all(&self) -> Result<Vec<NetworkRepoModel>, RepositoryError> {
88        match self {
89            NetworkRepositoryStorage::InMemory(repo) => repo.list_all().await,
90            NetworkRepositoryStorage::Redis(repo) => repo.list_all().await,
91        }
92    }
93
94    async fn list_paginated(
95        &self,
96        query: PaginationQuery,
97    ) -> Result<PaginatedResult<NetworkRepoModel>, RepositoryError> {
98        match self {
99            NetworkRepositoryStorage::InMemory(repo) => repo.list_paginated(query).await,
100            NetworkRepositoryStorage::Redis(repo) => repo.list_paginated(query).await,
101        }
102    }
103
104    async fn update(
105        &self,
106        id: String,
107        entity: NetworkRepoModel,
108    ) -> Result<NetworkRepoModel, RepositoryError> {
109        match self {
110            NetworkRepositoryStorage::InMemory(repo) => repo.update(id, entity).await,
111            NetworkRepositoryStorage::Redis(repo) => repo.update(id, entity).await,
112        }
113    }
114
115    async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
116        match self {
117            NetworkRepositoryStorage::InMemory(repo) => repo.delete_by_id(id).await,
118            NetworkRepositoryStorage::Redis(repo) => repo.delete_by_id(id).await,
119        }
120    }
121
122    async fn count(&self) -> Result<usize, RepositoryError> {
123        match self {
124            NetworkRepositoryStorage::InMemory(repo) => repo.count().await,
125            NetworkRepositoryStorage::Redis(repo) => repo.count().await,
126        }
127    }
128
129    async fn has_entries(&self) -> Result<bool, RepositoryError> {
130        match self {
131            NetworkRepositoryStorage::InMemory(repo) => repo.has_entries().await,
132            NetworkRepositoryStorage::Redis(repo) => repo.has_entries().await,
133        }
134    }
135
136    async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
137        match self {
138            NetworkRepositoryStorage::InMemory(repo) => repo.drop_all_entries().await,
139            NetworkRepositoryStorage::Redis(repo) => repo.drop_all_entries().await,
140        }
141    }
142}
143
144#[async_trait]
145impl NetworkRepository for NetworkRepositoryStorage {
146    async fn get_by_name(
147        &self,
148        network_type: NetworkType,
149        name: &str,
150    ) -> Result<Option<NetworkRepoModel>, RepositoryError> {
151        match self {
152            NetworkRepositoryStorage::InMemory(repo) => repo.get_by_name(network_type, name).await,
153            NetworkRepositoryStorage::Redis(repo) => repo.get_by_name(network_type, name).await,
154        }
155    }
156
157    async fn get_by_chain_id(
158        &self,
159        network_type: NetworkType,
160        chain_id: u64,
161    ) -> Result<Option<NetworkRepoModel>, RepositoryError> {
162        match self {
163            NetworkRepositoryStorage::InMemory(repo) => {
164                repo.get_by_chain_id(network_type, chain_id).await
165            }
166            NetworkRepositoryStorage::Redis(repo) => {
167                repo.get_by_chain_id(network_type, chain_id).await
168            }
169        }
170    }
171}
172
173#[cfg(test)]
174mockall::mock! {
175    pub NetworkRepository {}
176
177    #[async_trait]
178    impl Repository<NetworkRepoModel, String> for NetworkRepository {
179        async fn create(&self, entity: NetworkRepoModel) -> Result<NetworkRepoModel, RepositoryError>;
180        async fn get_by_id(&self, id: String) -> Result<NetworkRepoModel, RepositoryError>;
181        async fn list_all(&self) -> Result<Vec<NetworkRepoModel>, RepositoryError>;
182        async fn list_paginated(&self, query: PaginationQuery) -> Result<PaginatedResult<NetworkRepoModel>, RepositoryError>;
183        async fn update(&self, id: String, entity: NetworkRepoModel) -> Result<NetworkRepoModel, RepositoryError>;
184        async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError>;
185        async fn count(&self) -> Result<usize, RepositoryError>;
186        async fn has_entries(&self) -> Result<bool, RepositoryError>;
187        async fn drop_all_entries(&self) -> Result<(), RepositoryError>;
188    }
189
190    #[async_trait]
191    impl NetworkRepository for NetworkRepository {
192        async fn get_by_name(&self, network_type: NetworkType, name: &str) -> Result<Option<NetworkRepoModel>, RepositoryError>;
193        async fn get_by_chain_id(&self, network_type: NetworkType, chain_id: u64) -> Result<Option<NetworkRepoModel>, RepositoryError>;
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use crate::utils::mocks::mockutils::create_mock_network;
201    #[tokio::test]
202    async fn test_trait_methods_accessibility() {
203        // Create in-memory repository through the storage enum
204        let repo: NetworkRepositoryStorage = NetworkRepositoryStorage::new_in_memory();
205
206        // These methods are now accessible through the trait!
207        assert!(!repo.has_entries().await.unwrap());
208
209        // Add a network
210        let network = create_mock_network();
211        repo.create(network).await.unwrap();
212
213        // Check entries exist
214        assert!(repo.has_entries().await.unwrap());
215
216        // Drop all entries
217        repo.drop_all_entries().await.unwrap();
218
219        // Verify everything is cleaned up
220        assert!(!repo.has_entries().await.unwrap());
221        assert_eq!(repo.count().await.unwrap(), 0);
222    }
223}