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            if config.storage_encryption_key.is_none() {
53                warn!("⚠️ Storage encryption key is not set. Please set the STORAGE_ENCRYPTION_KEY environment variable.");
54                return Err(eyre::eyre!("Storage encryption key is not set. Please set the STORAGE_ENCRYPTION_KEY environment variable."));
55            }
56
57            let connection_manager = initialize_redis_connection(config).await?;
58
59            RepositoryCollection {
60                relayer: Arc::new(RelayerRepositoryStorage::new_redis(
61                    connection_manager.clone(),
62                    config.redis_key_prefix.clone(),
63                )?),
64                transaction: Arc::new(TransactionRepositoryStorage::new_redis(
65                    connection_manager.clone(),
66                    config.redis_key_prefix.clone(),
67                )?),
68                signer: Arc::new(SignerRepositoryStorage::new_redis(
69                    connection_manager.clone(),
70                    config.redis_key_prefix.clone(),
71                )?),
72                notification: Arc::new(NotificationRepositoryStorage::new_redis(
73                    connection_manager.clone(),
74                    config.redis_key_prefix.clone(),
75                )?),
76                network: Arc::new(NetworkRepositoryStorage::new_redis(
77                    connection_manager.clone(),
78                    config.redis_key_prefix.clone(),
79                )?),
80                transaction_counter: Arc::new(TransactionCounterRepositoryStorage::new_redis(
81                    connection_manager.clone(),
82                    config.redis_key_prefix.clone(),
83                )?),
84                plugin: Arc::new(PluginRepositoryStorage::new_redis(
85                    connection_manager.clone(),
86                    config.redis_key_prefix.clone(),
87                )?),
88                api_key: Arc::new(ApiKeyRepositoryStorage::new_redis(
89                    connection_manager,
90                    config.redis_key_prefix.clone(),
91                )?),
92            }
93        }
94    };
95
96    Ok(repositories)
97}
98
99/// Initializes application state
100///
101/// # Returns
102///
103/// * `Result<web::Data<AppState>>` - Initialized application state
104///
105/// # Errors
106///
107/// Returns error if:
108/// - Repository initialization fails
109/// - Configuration loading fails
110pub async fn initialize_app_state(
111    server_config: Arc<ServerConfig>,
112) -> Result<web::ThinData<DefaultAppState>> {
113    let repositories = initialize_repositories(&server_config).await?;
114
115    let queue = Queue::setup().await?;
116    let job_producer = Arc::new(jobs::JobProducer::new(queue.clone()));
117
118    let app_state = web::ThinData(AppState {
119        relayer_repository: repositories.relayer,
120        transaction_repository: repositories.transaction,
121        signer_repository: repositories.signer,
122        network_repository: repositories.network,
123        notification_repository: repositories.notification,
124        transaction_counter_store: repositories.transaction_counter,
125        job_producer,
126        plugin_repository: repositories.plugin,
127        api_key_repository: repositories.api_key,
128    });
129
130    Ok(app_state)
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::{
137        config::RepositoryStorageType,
138        repositories::{ApiKeyRepositoryTrait, Repository},
139        utils::mocks::mockutils::{
140            create_mock_api_key, create_mock_network, create_mock_relayer, create_mock_signer,
141            create_test_server_config,
142        },
143    };
144    use std::sync::Arc;
145
146    #[tokio::test]
147    async fn test_initialize_repositories_in_memory() {
148        let config = create_test_server_config(RepositoryStorageType::InMemory);
149        let result = initialize_repositories(&config).await;
150
151        assert!(result.is_ok());
152        let repositories = result.unwrap();
153
154        // Verify all repositories are created
155        assert!(Arc::strong_count(&repositories.relayer) >= 1);
156        assert!(Arc::strong_count(&repositories.transaction) >= 1);
157        assert!(Arc::strong_count(&repositories.signer) >= 1);
158        assert!(Arc::strong_count(&repositories.notification) >= 1);
159        assert!(Arc::strong_count(&repositories.network) >= 1);
160        assert!(Arc::strong_count(&repositories.transaction_counter) >= 1);
161        assert!(Arc::strong_count(&repositories.plugin) >= 1);
162        assert!(Arc::strong_count(&repositories.api_key) >= 1);
163    }
164
165    #[tokio::test]
166    async fn test_repository_collection_functionality() {
167        let config = create_test_server_config(RepositoryStorageType::InMemory);
168        let repositories = initialize_repositories(&config).await.unwrap();
169
170        // Test basic repository operations
171        let relayer = create_mock_relayer("test-relayer".to_string(), false);
172        let signer = create_mock_signer();
173        let network = create_mock_network();
174        let api_key = create_mock_api_key();
175
176        // Test creating and retrieving items
177        repositories.relayer.create(relayer.clone()).await.unwrap();
178        repositories.signer.create(signer.clone()).await.unwrap();
179        repositories.network.create(network.clone()).await.unwrap();
180        repositories.api_key.create(api_key.clone()).await.unwrap();
181
182        let retrieved_relayer = repositories
183            .relayer
184            .get_by_id("test-relayer".to_string())
185            .await
186            .unwrap();
187        let retrieved_signer = repositories
188            .signer
189            .get_by_id("test".to_string())
190            .await
191            .unwrap();
192        let retrieved_network = repositories
193            .network
194            .get_by_id("test".to_string())
195            .await
196            .unwrap();
197        let retrieved_api_key = repositories
198            .api_key
199            .get_by_id("test-api-key")
200            .await
201            .unwrap();
202
203        assert_eq!(retrieved_relayer.id, "test-relayer");
204        assert_eq!(retrieved_signer.id, "test");
205        assert_eq!(retrieved_network.id, "test");
206        assert_eq!(retrieved_api_key.unwrap().id, "test-api-key");
207    }
208
209    #[tokio::test]
210    async fn test_initialize_app_state_repository_error() {
211        let mut config = create_test_server_config(RepositoryStorageType::Redis);
212        config.redis_url = "redis://invalid_url".to_string();
213
214        let result = initialize_app_state(Arc::new(config)).await;
215
216        // Should fail during repository initialization
217        assert!(result.is_err());
218        let error = result.unwrap_err();
219        assert!(error.to_string().contains("Redis") || error.to_string().contains("connection"));
220    }
221}