openzeppelin_relayer/repositories/plugin/
mod.rs1pub mod plugin_in_memory;
26pub mod plugin_redis;
27
28pub use plugin_in_memory::*;
29pub use plugin_redis::*;
30
31use async_trait::async_trait;
32use redis::aio::ConnectionManager;
33use std::{sync::Arc, time::Duration};
34
35#[cfg(test)]
36use mockall::automock;
37
38use crate::{
39 config::PluginFileConfig,
40 constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS,
41 models::{PaginationQuery, PluginModel, RepositoryError},
42 repositories::{ConversionError, PaginatedResult},
43};
44
45#[async_trait]
46#[allow(dead_code)]
47#[cfg_attr(test, automock)]
48pub trait PluginRepositoryTrait {
49 async fn get_by_id(&self, id: &str) -> Result<Option<PluginModel>, RepositoryError>;
50 async fn add(&self, plugin: PluginModel) -> Result<(), RepositoryError>;
51 async fn list_paginated(
52 &self,
53 query: PaginationQuery,
54 ) -> Result<PaginatedResult<PluginModel>, RepositoryError>;
55 async fn count(&self) -> Result<usize, RepositoryError>;
56 async fn has_entries(&self) -> Result<bool, RepositoryError>;
57 async fn drop_all_entries(&self) -> Result<(), RepositoryError>;
58}
59
60#[derive(Debug, Clone)]
62pub enum PluginRepositoryStorage {
63 InMemory(InMemoryPluginRepository),
64 Redis(RedisPluginRepository),
65}
66
67impl PluginRepositoryStorage {
68 pub fn new_in_memory() -> Self {
69 Self::InMemory(InMemoryPluginRepository::new())
70 }
71
72 pub fn new_redis(
73 connection_manager: Arc<ConnectionManager>,
74 key_prefix: String,
75 ) -> Result<Self, RepositoryError> {
76 let redis_repo = RedisPluginRepository::new(connection_manager, key_prefix)?;
77 Ok(Self::Redis(redis_repo))
78 }
79}
80
81#[async_trait]
82impl PluginRepositoryTrait for PluginRepositoryStorage {
83 async fn get_by_id(&self, id: &str) -> Result<Option<PluginModel>, RepositoryError> {
84 match self {
85 PluginRepositoryStorage::InMemory(repo) => repo.get_by_id(id).await,
86 PluginRepositoryStorage::Redis(repo) => repo.get_by_id(id).await,
87 }
88 }
89
90 async fn add(&self, plugin: PluginModel) -> Result<(), RepositoryError> {
91 match self {
92 PluginRepositoryStorage::InMemory(repo) => repo.add(plugin).await,
93 PluginRepositoryStorage::Redis(repo) => repo.add(plugin).await,
94 }
95 }
96
97 async fn list_paginated(
98 &self,
99 query: PaginationQuery,
100 ) -> Result<PaginatedResult<PluginModel>, RepositoryError> {
101 match self {
102 PluginRepositoryStorage::InMemory(repo) => repo.list_paginated(query).await,
103 PluginRepositoryStorage::Redis(repo) => repo.list_paginated(query).await,
104 }
105 }
106
107 async fn count(&self) -> Result<usize, RepositoryError> {
108 match self {
109 PluginRepositoryStorage::InMemory(repo) => repo.count().await,
110 PluginRepositoryStorage::Redis(repo) => repo.count().await,
111 }
112 }
113
114 async fn has_entries(&self) -> Result<bool, RepositoryError> {
115 match self {
116 PluginRepositoryStorage::InMemory(repo) => repo.has_entries().await,
117 PluginRepositoryStorage::Redis(repo) => repo.has_entries().await,
118 }
119 }
120
121 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
122 match self {
123 PluginRepositoryStorage::InMemory(repo) => repo.drop_all_entries().await,
124 PluginRepositoryStorage::Redis(repo) => repo.drop_all_entries().await,
125 }
126 }
127}
128
129impl TryFrom<PluginFileConfig> for PluginModel {
130 type Error = ConversionError;
131
132 fn try_from(config: PluginFileConfig) -> Result<Self, Self::Error> {
133 let timeout = Duration::from_secs(config.timeout.unwrap_or(DEFAULT_PLUGIN_TIMEOUT_SECONDS));
134
135 Ok(PluginModel {
136 id: config.id.clone(),
137 path: config.path.clone(),
138 timeout,
139 })
140 }
141}
142
143impl PartialEq for PluginModel {
144 fn eq(&self, other: &Self) -> bool {
145 self.id == other.id && self.path == other.path
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use crate::{config::PluginFileConfig, constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS};
152 use std::time::Duration;
153
154 use super::*;
155
156 #[tokio::test]
157 async fn test_try_from() {
158 let plugin = PluginFileConfig {
159 id: "test-plugin".to_string(),
160 path: "test-path".to_string(),
161 timeout: None,
162 };
163 let result = PluginModel::try_from(plugin);
164 assert!(result.is_ok());
165 assert_eq!(
166 result.unwrap(),
167 PluginModel {
168 id: "test-plugin".to_string(),
169 path: "test-path".to_string(),
170 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
171 }
172 );
173 }
174
175 fn create_test_plugin(id: &str, path: &str) -> PluginModel {
177 PluginModel {
178 id: id.to_string(),
179 path: path.to_string(),
180 timeout: Duration::from_secs(30),
181 }
182 }
183
184 #[tokio::test]
185 async fn test_plugin_repository_storage_get_by_id_existing() {
186 let storage = PluginRepositoryStorage::new_in_memory();
187 let plugin = create_test_plugin("test-plugin", "/path/to/script.js");
188
189 storage.add(plugin.clone()).await.unwrap();
191
192 let result = storage.get_by_id("test-plugin").await.unwrap();
194 assert_eq!(result, Some(plugin));
195 }
196
197 #[tokio::test]
198 async fn test_plugin_repository_storage_get_by_id_non_existing() {
199 let storage = PluginRepositoryStorage::new_in_memory();
200
201 let result = storage.get_by_id("non-existent").await.unwrap();
202 assert_eq!(result, None);
203 }
204
205 #[tokio::test]
206 async fn test_plugin_repository_storage_add_success() {
207 let storage = PluginRepositoryStorage::new_in_memory();
208 let plugin = create_test_plugin("test-plugin", "/path/to/script.js");
209
210 let result = storage.add(plugin).await;
211 assert!(result.is_ok());
212 }
213
214 #[tokio::test]
215 async fn test_plugin_repository_storage_add_duplicate() {
216 let storage = PluginRepositoryStorage::new_in_memory();
217 let plugin = create_test_plugin("test-plugin", "/path/to/script.js");
218
219 storage.add(plugin.clone()).await.unwrap();
221
222 let result = storage.add(plugin).await;
224 assert!(result.is_ok());
225 }
226
227 #[tokio::test]
228 async fn test_plugin_repository_storage_count_empty() {
229 let storage = PluginRepositoryStorage::new_in_memory();
230
231 let count = storage.count().await.unwrap();
232 assert_eq!(count, 0);
233 }
234
235 #[tokio::test]
236 async fn test_plugin_repository_storage_count_with_plugins() {
237 let storage = PluginRepositoryStorage::new_in_memory();
238
239 storage
241 .add(create_test_plugin("plugin1", "/path/1.js"))
242 .await
243 .unwrap();
244 storage
245 .add(create_test_plugin("plugin2", "/path/2.js"))
246 .await
247 .unwrap();
248 storage
249 .add(create_test_plugin("plugin3", "/path/3.js"))
250 .await
251 .unwrap();
252
253 let count = storage.count().await.unwrap();
254 assert_eq!(count, 3);
255 }
256
257 #[tokio::test]
258 async fn test_plugin_repository_storage_has_entries_empty() {
259 let storage = PluginRepositoryStorage::new_in_memory();
260
261 let has_entries = storage.has_entries().await.unwrap();
262 assert!(!has_entries);
263 }
264
265 #[tokio::test]
266 async fn test_plugin_repository_storage_has_entries_with_plugins() {
267 let storage = PluginRepositoryStorage::new_in_memory();
268
269 storage
270 .add(create_test_plugin("plugin1", "/path/1.js"))
271 .await
272 .unwrap();
273
274 let has_entries = storage.has_entries().await.unwrap();
275 assert!(has_entries);
276 }
277
278 #[tokio::test]
279 async fn test_plugin_repository_storage_drop_all_entries_empty() {
280 let storage = PluginRepositoryStorage::new_in_memory();
281
282 let result = storage.drop_all_entries().await;
283 assert!(result.is_ok());
284
285 let count = storage.count().await.unwrap();
286 assert_eq!(count, 0);
287 }
288
289 #[tokio::test]
290 async fn test_plugin_repository_storage_drop_all_entries_with_plugins() {
291 let storage = PluginRepositoryStorage::new_in_memory();
292
293 storage
295 .add(create_test_plugin("plugin1", "/path/1.js"))
296 .await
297 .unwrap();
298 storage
299 .add(create_test_plugin("plugin2", "/path/2.js"))
300 .await
301 .unwrap();
302
303 let result = storage.drop_all_entries().await;
304 assert!(result.is_ok());
305
306 let count = storage.count().await.unwrap();
307 assert_eq!(count, 0);
308
309 let has_entries = storage.has_entries().await.unwrap();
310 assert!(!has_entries);
311 }
312
313 #[tokio::test]
314 async fn test_plugin_repository_storage_list_paginated_empty() {
315 let storage = PluginRepositoryStorage::new_in_memory();
316
317 let query = PaginationQuery {
318 page: 1,
319 per_page: 10,
320 };
321 let result = storage.list_paginated(query).await.unwrap();
322
323 assert_eq!(result.items.len(), 0);
324 assert_eq!(result.total, 0);
325 assert_eq!(result.page, 1);
326 assert_eq!(result.per_page, 10);
327 }
328
329 #[tokio::test]
330 async fn test_plugin_repository_storage_list_paginated_with_plugins() {
331 let storage = PluginRepositoryStorage::new_in_memory();
332
333 storage
335 .add(create_test_plugin("plugin1", "/path/1.js"))
336 .await
337 .unwrap();
338 storage
339 .add(create_test_plugin("plugin2", "/path/2.js"))
340 .await
341 .unwrap();
342 storage
343 .add(create_test_plugin("plugin3", "/path/3.js"))
344 .await
345 .unwrap();
346
347 let query = PaginationQuery {
348 page: 1,
349 per_page: 2,
350 };
351 let result = storage.list_paginated(query).await.unwrap();
352
353 assert_eq!(result.items.len(), 2);
354 assert_eq!(result.total, 3);
355 assert_eq!(result.page, 1);
356 assert_eq!(result.per_page, 2);
357 }
358
359 #[tokio::test]
360 async fn test_plugin_repository_storage_workflow() {
361 let storage = PluginRepositoryStorage::new_in_memory();
362
363 assert!(!storage.has_entries().await.unwrap());
365 assert_eq!(storage.count().await.unwrap(), 0);
366
367 let plugin1 = create_test_plugin("auth-plugin", "/scripts/auth.js");
369 let plugin2 = create_test_plugin("email-plugin", "/scripts/email.js");
370
371 storage.add(plugin1.clone()).await.unwrap();
372 storage.add(plugin2.clone()).await.unwrap();
373
374 assert!(storage.has_entries().await.unwrap());
376 assert_eq!(storage.count().await.unwrap(), 2);
377
378 let retrieved = storage.get_by_id("auth-plugin").await.unwrap();
380 assert_eq!(retrieved, Some(plugin1));
381
382 let query = PaginationQuery {
384 page: 1,
385 per_page: 10,
386 };
387 let result = storage.list_paginated(query).await.unwrap();
388 assert_eq!(result.items.len(), 2);
389 assert_eq!(result.total, 2);
390
391 storage.drop_all_entries().await.unwrap();
393 assert!(!storage.has_entries().await.unwrap());
394 assert_eq!(storage.count().await.unwrap(), 0);
395 }
396}