openzeppelin_relayer/repositories/api_key/
mod.rs1use async_trait::async_trait;
19use redis::aio::ConnectionManager;
20use std::sync::Arc;
21
22pub mod api_key_in_memory;
23pub mod api_key_redis;
24
25pub use api_key_in_memory::*;
26pub use api_key_redis::*;
27
28#[cfg(test)]
29use mockall::automock;
30
31use crate::{
32 models::{ApiKeyRepoModel, PaginationQuery, RepositoryError},
33 repositories::PaginatedResult,
34};
35
36#[async_trait]
37#[allow(dead_code)]
38#[cfg_attr(test, automock)]
39pub trait ApiKeyRepositoryTrait {
40 async fn get_by_id(&self, id: &str) -> Result<Option<ApiKeyRepoModel>, RepositoryError>;
41 async fn create(&self, api_key: ApiKeyRepoModel) -> Result<ApiKeyRepoModel, RepositoryError>;
42 async fn list_paginated(
43 &self,
44 query: PaginationQuery,
45 ) -> Result<PaginatedResult<ApiKeyRepoModel>, RepositoryError>;
46 async fn count(&self) -> Result<usize, RepositoryError>;
47 async fn list_permissions(&self, api_key_id: &str) -> Result<Vec<String>, RepositoryError>;
48 async fn delete_by_id(&self, api_key_id: &str) -> Result<(), RepositoryError>;
49 async fn has_entries(&self) -> Result<bool, RepositoryError>;
50 async fn drop_all_entries(&self) -> Result<(), RepositoryError>;
51}
52
53#[derive(Debug, Clone)]
55pub enum ApiKeyRepositoryStorage {
56 InMemory(InMemoryApiKeyRepository),
57 Redis(RedisApiKeyRepository),
58}
59
60impl ApiKeyRepositoryStorage {
61 pub fn new_in_memory() -> Self {
62 Self::InMemory(InMemoryApiKeyRepository::new())
63 }
64
65 pub fn new_redis(
66 connection_manager: Arc<ConnectionManager>,
67 key_prefix: String,
68 ) -> Result<Self, RepositoryError> {
69 let redis_repo = RedisApiKeyRepository::new(connection_manager, key_prefix)?;
70 Ok(Self::Redis(redis_repo))
71 }
72}
73
74#[async_trait]
75impl ApiKeyRepositoryTrait for ApiKeyRepositoryStorage {
76 async fn get_by_id(&self, id: &str) -> Result<Option<ApiKeyRepoModel>, RepositoryError> {
77 match self {
78 ApiKeyRepositoryStorage::InMemory(repo) => repo.get_by_id(id).await,
79 ApiKeyRepositoryStorage::Redis(repo) => repo.get_by_id(id).await,
80 }
81 }
82
83 async fn create(&self, api_key: ApiKeyRepoModel) -> Result<ApiKeyRepoModel, RepositoryError> {
84 match self {
85 ApiKeyRepositoryStorage::InMemory(repo) => repo.create(api_key).await,
86 ApiKeyRepositoryStorage::Redis(repo) => repo.create(api_key).await,
87 }
88 }
89
90 async fn list_permissions(&self, api_key_id: &str) -> Result<Vec<String>, RepositoryError> {
91 match self {
92 ApiKeyRepositoryStorage::InMemory(repo) => repo.list_permissions(api_key_id).await,
93 ApiKeyRepositoryStorage::Redis(repo) => repo.list_permissions(api_key_id).await,
94 }
95 }
96
97 async fn delete_by_id(&self, api_key_id: &str) -> Result<(), RepositoryError> {
98 match self {
99 ApiKeyRepositoryStorage::InMemory(repo) => repo.delete_by_id(api_key_id).await,
100 ApiKeyRepositoryStorage::Redis(repo) => repo.delete_by_id(api_key_id).await,
101 }
102 }
103
104 async fn list_paginated(
105 &self,
106 query: PaginationQuery,
107 ) -> Result<PaginatedResult<ApiKeyRepoModel>, RepositoryError> {
108 match self {
109 ApiKeyRepositoryStorage::InMemory(repo) => repo.list_paginated(query).await,
110 ApiKeyRepositoryStorage::Redis(repo) => repo.list_paginated(query).await,
111 }
112 }
113
114 async fn count(&self) -> Result<usize, RepositoryError> {
115 match self {
116 ApiKeyRepositoryStorage::InMemory(repo) => repo.count().await,
117 ApiKeyRepositoryStorage::Redis(repo) => repo.count().await,
118 }
119 }
120
121 async fn has_entries(&self) -> Result<bool, RepositoryError> {
122 match self {
123 ApiKeyRepositoryStorage::InMemory(repo) => repo.has_entries().await,
124 ApiKeyRepositoryStorage::Redis(repo) => repo.has_entries().await,
125 }
126 }
127
128 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
129 match self {
130 ApiKeyRepositoryStorage::InMemory(repo) => repo.drop_all_entries().await,
131 ApiKeyRepositoryStorage::Redis(repo) => repo.drop_all_entries().await,
132 }
133 }
134}
135
136impl PartialEq for ApiKeyRepoModel {
137 fn eq(&self, other: &Self) -> bool {
138 self.id == other.id
139 && self.name == other.name
140 && self.allowed_origins == other.allowed_origins
141 && self.permissions == other.permissions
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use crate::models::SecretString;
148
149 use super::*;
150
151 use chrono::Utc;
152
153 fn create_test_api_key(
155 id: &str,
156 name: &str,
157 value: &str,
158 allowed_origins: &[&str],
159 permissions: &[&str],
160 ) -> ApiKeyRepoModel {
161 ApiKeyRepoModel {
162 id: id.to_string(),
163 name: name.to_string(),
164 value: SecretString::new(value),
165 allowed_origins: allowed_origins.iter().map(|s| s.to_string()).collect(),
166 permissions: permissions.iter().map(|s| s.to_string()).collect(),
167 created_at: Utc::now().to_string(),
168 }
169 }
170
171 #[tokio::test]
172 async fn test_api_key_repository_storage_get_by_id_existing() {
173 let storage = ApiKeyRepositoryStorage::new_in_memory();
174 let api_key = create_test_api_key(
175 "test-api-key",
176 "test-name",
177 "test-value",
178 &["*"],
179 &["relayer:all:execute"],
180 );
181
182 storage.create(api_key.clone()).await.unwrap();
184
185 let result = storage.get_by_id("test-api-key").await.unwrap();
187 assert_eq!(result, Some(api_key));
188 }
189
190 #[tokio::test]
191 async fn test_api_key_repository_storage_get_by_id_non_existing() {
192 let storage = ApiKeyRepositoryStorage::new_in_memory();
193
194 let result = storage.get_by_id("non-existent").await.unwrap();
195 assert_eq!(result, None);
196 }
197
198 #[tokio::test]
199 async fn test_api_key_repository_storage_add_success() {
200 let storage = ApiKeyRepositoryStorage::new_in_memory();
201 let api_key = create_test_api_key(
202 "test-api-key",
203 "test-name",
204 "test-value",
205 &["*"],
206 &["relayer:all:execute"],
207 );
208
209 let result = storage.create(api_key).await;
210 assert!(result.is_ok());
211 }
212
213 #[tokio::test]
214 async fn test_api_key_repository_storage_add_duplicate() {
215 let storage = ApiKeyRepositoryStorage::new_in_memory();
216 let api_key = create_test_api_key(
217 "test-api-key",
218 "test-name",
219 "test-value",
220 &["*"],
221 &["relayer:all:execute"],
222 );
223
224 storage.create(api_key.clone()).await.unwrap();
226
227 let result = storage.create(api_key).await;
229 assert!(result.is_ok());
230 }
231
232 #[tokio::test]
233 async fn test_api_key_repository_storage_count_empty() {
234 let storage = ApiKeyRepositoryStorage::new_in_memory();
235
236 let count = storage.count().await.unwrap();
237 assert_eq!(count, 0);
238 }
239
240 #[tokio::test]
241 async fn test_api_key_repository_storage_count_with_api_keys() {
242 let storage = ApiKeyRepositoryStorage::new_in_memory();
243
244 storage
246 .create(create_test_api_key(
247 "api-key1",
248 "test-name1",
249 "test-value1",
250 &["*"],
251 &["relayer:all:execute"],
252 ))
253 .await
254 .unwrap();
255 storage
256 .create(create_test_api_key(
257 "api-key2",
258 "test-name2",
259 "test-value2",
260 &["*"],
261 &["relayer:all:execute"],
262 ))
263 .await
264 .unwrap();
265 storage
266 .create(create_test_api_key(
267 "api-key3",
268 "test-name3",
269 "test-value3",
270 &["*"],
271 &["relayer:all:execute"],
272 ))
273 .await
274 .unwrap();
275
276 let count = storage.count().await.unwrap();
277 assert_eq!(count, 3);
278 }
279
280 #[tokio::test]
281 async fn test_api_key_repository_storage_has_entries_empty() {
282 let storage = ApiKeyRepositoryStorage::new_in_memory();
283
284 let has_entries = storage.has_entries().await.unwrap();
285 assert!(!has_entries);
286 }
287
288 #[tokio::test]
289 async fn test_api_key_repository_storage_has_entries_with_api_keys() {
290 let storage = ApiKeyRepositoryStorage::new_in_memory();
291
292 storage
293 .create(create_test_api_key(
294 "api-key1",
295 "test-name1",
296 "test-value1",
297 &["*"],
298 &["relayer:all:execute"],
299 ))
300 .await
301 .unwrap();
302
303 let has_entries = storage.has_entries().await.unwrap();
304 assert!(has_entries);
305 }
306
307 #[tokio::test]
308 async fn test_api_key_repository_storage_drop_all_entries_empty() {
309 let storage = ApiKeyRepositoryStorage::new_in_memory();
310
311 let result = storage.drop_all_entries().await;
312 assert!(result.is_ok());
313
314 let count = storage.count().await.unwrap();
315 assert_eq!(count, 0);
316 }
317
318 #[tokio::test]
319 async fn test_api_key_repository_storage_drop_all_entries_with_api_keys() {
320 let storage = ApiKeyRepositoryStorage::new_in_memory();
321
322 storage
324 .create(create_test_api_key(
325 "api-key1",
326 "test-name1",
327 "test-value1",
328 &["*"],
329 &["relayer:all:execute"],
330 ))
331 .await
332 .unwrap();
333 storage
334 .create(create_test_api_key(
335 "api-key2",
336 "test-name2",
337 "test-value2",
338 &["*"],
339 &["relayer:all:execute"],
340 ))
341 .await
342 .unwrap();
343
344 let result = storage.drop_all_entries().await;
345 assert!(result.is_ok());
346
347 let count = storage.count().await.unwrap();
348 assert_eq!(count, 0);
349
350 let has_entries = storage.has_entries().await.unwrap();
351 assert!(!has_entries);
352 }
353
354 #[tokio::test]
355 async fn test_api_key_repository_storage_list_paginated_empty() {
356 let storage = ApiKeyRepositoryStorage::new_in_memory();
357
358 let query = PaginationQuery {
359 page: 1,
360 per_page: 10,
361 };
362 let result = storage.list_paginated(query).await.unwrap();
363
364 assert_eq!(result.items.len(), 0);
365 assert_eq!(result.total, 0);
366 assert_eq!(result.page, 1);
367 assert_eq!(result.per_page, 10);
368 }
369
370 #[tokio::test]
371 async fn test_api_key_repository_storage_list_paginated_with_api_keys() {
372 let storage = ApiKeyRepositoryStorage::new_in_memory();
373
374 storage
376 .create(create_test_api_key(
377 "api-key1",
378 "test-name1",
379 "test-value1",
380 &["*"],
381 &["relayer:all:execute"],
382 ))
383 .await
384 .unwrap();
385 storage
386 .create(create_test_api_key(
387 "api-key2",
388 "test-name2",
389 "test-value2",
390 &["*"],
391 &["relayer:all:execute"],
392 ))
393 .await
394 .unwrap();
395 storage
396 .create(create_test_api_key(
397 "api-key3",
398 "test-name3",
399 "test-value3",
400 &["*"],
401 &["relayer:all:execute"],
402 ))
403 .await
404 .unwrap();
405
406 let query = PaginationQuery {
407 page: 1,
408 per_page: 2,
409 };
410 let result = storage.list_paginated(query).await.unwrap();
411
412 assert_eq!(result.items.len(), 2);
413 assert_eq!(result.total, 3);
414 assert_eq!(result.page, 1);
415 assert_eq!(result.per_page, 2);
416 }
417
418 #[tokio::test]
419 async fn test_api_key_repository_storage_workflow() {
420 let storage = ApiKeyRepositoryStorage::new_in_memory();
421
422 assert!(!storage.has_entries().await.unwrap());
424 assert_eq!(storage.count().await.unwrap(), 0);
425
426 let api_key1 = create_test_api_key(
428 "api-key1",
429 "test-name1",
430 "test-value1",
431 &["*"],
432 &["relayer:all:execute"],
433 );
434 let api_key2 = create_test_api_key(
435 "api-key2",
436 "test-name2",
437 "test-value2",
438 &["*"],
439 &["relayer:all:execute"],
440 );
441
442 storage.create(api_key1.clone()).await.unwrap();
443 storage.create(api_key2.clone()).await.unwrap();
444
445 assert!(storage.has_entries().await.unwrap());
447 assert_eq!(storage.count().await.unwrap(), 2);
448
449 let retrieved = storage.get_by_id("api-key1").await.unwrap();
451 assert_eq!(retrieved, Some(api_key1));
452
453 let query = PaginationQuery {
455 page: 1,
456 per_page: 10,
457 };
458 let result = storage.list_paginated(query).await.unwrap();
459 assert_eq!(result.items.len(), 2);
460 assert_eq!(result.total, 2);
461
462 storage.drop_all_entries().await.unwrap();
464 assert!(!storage.has_entries().await.unwrap());
465 assert_eq!(storage.count().await.unwrap(), 0);
466 }
467}