openzeppelin_relayer/bootstrap/
initialize_app_state.rs

1//! Application state initialization
2//!
3//! This module contains functions for initializing the application state,
4//! including setting up repositories, job queues, and other necessary components.
5use crate::{
6    config::{RepositoryStorageType, ServerConfig},
7    jobs::{self, Queue},
8    models::{AppState, DefaultAppState},
9    repositories::{
10        ApiKeyRepositoryStorage, NetworkRepositoryStorage, NotificationRepositoryStorage,
11        PluginRepositoryStorage, RelayerRepositoryStorage, SignerRepositoryStorage,
12        TransactionCounterRepositoryStorage, TransactionRepositoryStorage,
13    },
14    utils::initialize_redis_connection,
15};
16use actix_web::web;
17use color_eyre::Result;
18use std::sync::Arc;
19use tracing::warn;
20
21pub struct RepositoryCollection {
22    pub relayer: Arc<RelayerRepositoryStorage>,
23    pub transaction: Arc<TransactionRepositoryStorage>,
24    pub signer: Arc<SignerRepositoryStorage>,
25    pub notification: Arc<NotificationRepositoryStorage>,
26    pub network: Arc<NetworkRepositoryStorage>,
27    pub transaction_counter: Arc<TransactionCounterRepositoryStorage>,
28    pub plugin: Arc<PluginRepositoryStorage>,
29    pub api_key: Arc<ApiKeyRepositoryStorage>,
30}
31
32/// Initializes repositories based on the server configuration
33///
34/// # Returns
35///
36/// * `Result<RepositoryCollection>` - Initialized repositories
37///
38/// # Errors
39pub async fn initialize_repositories(config: &ServerConfig) -> eyre::Result<RepositoryCollection> {
40    let repositories = match config.repository_storage_type {
41        RepositoryStorageType::InMemory => RepositoryCollection {
42            relayer: Arc::new(RelayerRepositoryStorage::new_in_memory()),
43            transaction: Arc::new(TransactionRepositoryStorage::new_in_memory()),
44            signer: Arc::new(SignerRepositoryStorage::new_in_memory()),
45            notification: Arc::new(NotificationRepositoryStorage::new_in_memory()),
46            network: Arc::new(NetworkRepositoryStorage::new_in_memory()),
47            transaction_counter: Arc::new(TransactionCounterRepositoryStorage::new_in_memory()),
48            plugin: Arc::new(PluginRepositoryStorage::new_in_memory()),
49            api_key: Arc::new(ApiKeyRepositoryStorage::new_in_memory()),
50        },
51        RepositoryStorageType::Redis => {
52            warn!("⚠️ Redis repository storage support is experimental. Use with caution.");
53
54            if config.storage_encryption_key.is_none() {
55                warn!("⚠️ Storage encryption key is not set. Please set the STORAGE_ENCRYPTION_KEY environment variable.");
56                return Err(eyre::eyre!("Storage encryption key is not set. Please set the STORAGE_ENCRYPTION_KEY environment variable."));
57            }
58
59            let connection_manager = initialize_redis_connection(config).await?;
60
61            RepositoryCollection {
62                relayer: Arc::new(RelayerRepositoryStorage::new_redis(
63                    connection_manager.clone(),
64                    config.redis_key_prefix.clone(),
65                )?),
66                transaction: Arc::new(TransactionRepositoryStorage::new_redis(
67                    connection_manager.clone(),
68                    config.redis_key_prefix.clone(),
69                )?),
70                signer: Arc::new(SignerRepositoryStorage::new_redis(
71                    connection_manager.clone(),
72                    config.redis_key_prefix.clone(),
73                )?),
74                notification: Arc::new(NotificationRepositoryStorage::new_redis(
75                    connection_manager.clone(),
76                    config.redis_key_prefix.clone(),
77                )?),
78                network: Arc::new(NetworkRepositoryStorage::new_redis(
79                    connection_manager.clone(),
80                    config.redis_key_prefix.clone(),
81                )?),
82                transaction_counter: Arc::new(TransactionCounterRepositoryStorage::new_redis(
83                    connection_manager.clone(),
84                    config.redis_key_prefix.clone(),
85                )?),
86                plugin: Arc::new(PluginRepositoryStorage::new_redis(
87                    connection_manager.clone(),
88                    config.redis_key_prefix.clone(),
89                )?),
90                api_key: Arc::new(ApiKeyRepositoryStorage::new_redis(
91                    connection_manager,
92                    config.redis_key_prefix.clone(),
93                )?),
94            }
95        }
96    };
97
98    Ok(repositories)
99}
100
101/// Initializes application state
102///
103/// # Returns
104///
105/// * `Result<web::Data<AppState>>` - Initialized application state
106///
107/// # Errors
108///
109/// Returns error if:
110/// - Repository initialization fails
111/// - Configuration loading fails
112pub async fn initialize_app_state(
113    server_config: Arc<ServerConfig>,
114) -> Result<web::ThinData<DefaultAppState>> {
115    let repositories = initialize_repositories(&server_config).await?;
116
117    let queue = Queue::setup().await?;
118    let job_producer = Arc::new(jobs::JobProducer::new(queue.clone()));
119
120    let app_state = web::ThinData(AppState {
121        relayer_repository: repositories.relayer,
122        transaction_repository: repositories.transaction,
123        signer_repository: repositories.signer,
124        network_repository: repositories.network,
125        notification_repository: repositories.notification,
126        transaction_counter_store: repositories.transaction_counter,
127        job_producer,
128        plugin_repository: repositories.plugin,
129        api_key_repository: repositories.api_key,
130    });
131
132    Ok(app_state)
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use crate::{
139        config::RepositoryStorageType,
140        repositories::{ApiKeyRepositoryTrait, Repository},
141        utils::mocks::mockutils::{
142            create_mock_api_key, create_mock_network, create_mock_relayer, create_mock_signer,
143            create_test_server_config,
144        },
145    };
146    use std::sync::Arc;
147
148    #[tokio::test]
149    async fn test_initialize_repositories_in_memory() {
150        let config = create_test_server_config(RepositoryStorageType::InMemory);
151        let result = initialize_repositories(&config).await;
152
153        assert!(result.is_ok());
154        let repositories = result.unwrap();
155
156        // Verify all repositories are created
157        assert!(Arc::strong_count(&repositories.relayer) >= 1);
158        assert!(Arc::strong_count(&repositories.transaction) >= 1);
159        assert!(Arc::strong_count(&repositories.signer) >= 1);
160        assert!(Arc::strong_count(&repositories.notification) >= 1);
161        assert!(Arc::strong_count(&repositories.network) >= 1);
162        assert!(Arc::strong_count(&repositories.transaction_counter) >= 1);
163        assert!(Arc::strong_count(&repositories.plugin) >= 1);
164        assert!(Arc::strong_count(&repositories.api_key) >= 1);
165    }
166
167    #[tokio::test]
168    async fn test_repository_collection_functionality() {
169        let config = create_test_server_config(RepositoryStorageType::InMemory);
170        let repositories = initialize_repositories(&config).await.unwrap();
171
172        // Test basic repository operations
173        let relayer = create_mock_relayer("test-relayer".to_string(), false);
174        let signer = create_mock_signer();
175        let network = create_mock_network();
176        let api_key = create_mock_api_key();
177
178        // Test creating and retrieving items
179        repositories.relayer.create(relayer.clone()).await.unwrap();
180        repositories.signer.create(signer.clone()).await.unwrap();
181        repositories.network.create(network.clone()).await.unwrap();
182        repositories.api_key.create(api_key.clone()).await.unwrap();
183
184        let retrieved_relayer = repositories
185            .relayer
186            .get_by_id("test-relayer".to_string())
187            .await
188            .unwrap();
189        let retrieved_signer = repositories
190            .signer
191            .get_by_id("test".to_string())
192            .await
193            .unwrap();
194        let retrieved_network = repositories
195            .network
196            .get_by_id("test".to_string())
197            .await
198            .unwrap();
199        let retrieved_api_key = repositories
200            .api_key
201            .get_by_id("test-api-key")
202            .await
203            .unwrap();
204
205        assert_eq!(retrieved_relayer.id, "test-relayer");
206        assert_eq!(retrieved_signer.id, "test");
207        assert_eq!(retrieved_network.id, "test");
208        assert_eq!(retrieved_api_key.unwrap().id, "test-api-key");
209    }
210
211    #[tokio::test]
212    async fn test_initialize_app_state_repository_error() {
213        let mut config = create_test_server_config(RepositoryStorageType::Redis);
214        config.redis_url = "redis://invalid_url".to_string();
215
216        let result = initialize_app_state(Arc::new(config)).await;
217
218        // Should fail during repository initialization
219        assert!(result.is_err());
220        let error = result.unwrap_err();
221        assert!(error.to_string().contains("Redis") || error.to_string().contains("connection"));
222    }
223}