openzeppelin_relayer/repositories/signer/
signer_in_memory.rs1use crate::{
5 models::{RepositoryError, SignerRepoModel},
6 repositories::*,
7};
8use async_trait::async_trait;
9use eyre::Result;
10use std::collections::HashMap;
11use tokio::sync::{Mutex, MutexGuard};
12
13#[derive(Debug)]
14pub struct InMemorySignerRepository {
15 store: Mutex<HashMap<String, SignerRepoModel>>,
16}
17
18impl Clone for InMemorySignerRepository {
19 fn clone(&self) -> Self {
20 let data = self
22 .store
23 .try_lock()
24 .map(|guard| guard.clone())
25 .unwrap_or_else(|_| HashMap::new());
26
27 Self {
28 store: Mutex::new(data),
29 }
30 }
31}
32
33#[allow(dead_code)]
34impl InMemorySignerRepository {
35 pub fn new() -> Self {
36 Self {
37 store: Mutex::new(HashMap::new()),
38 }
39 }
40
41 async fn acquire_lock<T>(lock: &Mutex<T>) -> Result<MutexGuard<T>, RepositoryError> {
42 Ok(lock.lock().await)
43 }
44}
45
46impl Default for InMemorySignerRepository {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52#[async_trait]
53impl Repository<SignerRepoModel, String> for InMemorySignerRepository {
54 async fn create(&self, signer: SignerRepoModel) -> Result<SignerRepoModel, RepositoryError> {
55 let mut store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
56 Self::acquire_lock(&self.store).await?;
57 if store.contains_key(&signer.id) {
58 return Err(RepositoryError::ConstraintViolation(format!(
59 "Signer with ID {} already exists",
60 signer.id
61 )));
62 }
63 store.insert(signer.id.clone(), signer.clone());
64 Ok(signer)
65 }
66
67 async fn get_by_id(&self, id: String) -> Result<SignerRepoModel, RepositoryError> {
68 let store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
69 Self::acquire_lock(&self.store).await?;
70 match store.get(&id) {
71 Some(signer) => Ok(signer.clone()),
72 None => Err(RepositoryError::NotFound(format!(
73 "Signer with ID {id} not found"
74 ))),
75 }
76 }
77
78 #[allow(clippy::map_entry)]
79 async fn update(
80 &self,
81 id: String,
82 signer: SignerRepoModel,
83 ) -> Result<SignerRepoModel, RepositoryError> {
84 let mut store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
85 Self::acquire_lock(&self.store).await?;
86 if !store.contains_key(&id) {
87 return Err(RepositoryError::NotFound(format!(
88 "Signer with ID {id} not found"
89 )));
90 }
91 store.insert(id, signer.clone());
92 Ok(signer)
93 }
94
95 async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
96 let mut store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
97 Self::acquire_lock(&self.store).await?;
98 if !store.contains_key(&id) {
99 return Err(RepositoryError::NotFound(format!(
100 "Signer with ID {id} not found"
101 )));
102 }
103 store.remove(&id);
104 Ok(())
105 }
106
107 async fn list_all(&self) -> Result<Vec<SignerRepoModel>, RepositoryError> {
108 let store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
109 Self::acquire_lock(&self.store).await?;
110 let signers: Vec<SignerRepoModel> = store.values().cloned().collect();
111 Ok(signers)
112 }
113
114 async fn list_paginated(
115 &self,
116 query: PaginationQuery,
117 ) -> Result<PaginatedResult<SignerRepoModel>, RepositoryError> {
118 let total = self.count().await?;
119 let start = ((query.page - 1) * query.per_page) as usize;
120 let items: Vec<SignerRepoModel> = self
121 .store
122 .lock()
123 .await
124 .values()
125 .skip(start)
126 .take(query.per_page as usize)
127 .cloned()
128 .collect();
129
130 Ok(PaginatedResult {
131 items,
132 total: total as u64,
133 page: query.page,
134 per_page: query.per_page,
135 })
136 }
137
138 async fn count(&self) -> Result<usize, RepositoryError> {
139 let store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
140 Self::acquire_lock(&self.store).await?;
141 let length = store.len();
142 Ok(length)
143 }
144
145 async fn has_entries(&self) -> Result<bool, RepositoryError> {
146 let store = Self::acquire_lock(&self.store).await?;
147 Ok(!store.is_empty())
148 }
149
150 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
151 let mut store = Self::acquire_lock(&self.store).await?;
152 store.clear();
153 Ok(())
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use secrets::SecretVec;
160
161 use crate::models::{LocalSignerConfigStorage, SignerConfigStorage};
162
163 use super::*;
164
165 fn create_test_signer(id: String) -> SignerRepoModel {
166 SignerRepoModel {
167 id: id.clone(),
168 config: SignerConfigStorage::Local(LocalSignerConfigStorage {
169 raw_key: SecretVec::zero(0),
170 }),
171 }
172 }
173
174 #[actix_web::test]
175 async fn test_new_repository_is_empty() {
176 let repo = InMemorySignerRepository::new();
177 assert_eq!(repo.count().await.unwrap(), 0);
178 }
179
180 #[actix_web::test]
181 async fn test_add_signer() {
182 let repo = InMemorySignerRepository::new();
183 let signer = create_test_signer("test".to_string());
184
185 repo.create(signer.clone()).await.unwrap();
186 assert_eq!(repo.count().await.unwrap(), 1);
187
188 let stored = repo.get_by_id("test".to_string()).await.unwrap();
189 assert_eq!(stored.id, signer.id);
190 }
191
192 #[actix_web::test]
193 async fn test_update_signer() {
194 let repo = InMemorySignerRepository::new();
195 let signer = create_test_signer("test".to_string());
196
197 repo.create(signer.clone()).await.unwrap();
199
200 let updated_signer = SignerRepoModel {
202 id: "test".to_string(),
203 config: SignerConfigStorage::Local(LocalSignerConfigStorage {
204 raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[2; 32])),
205 }),
206 };
207
208 let result = repo.update("test".to_string(), updated_signer).await;
209 assert!(result.is_ok());
210 }
211
212 #[actix_web::test]
213 async fn test_list_signers() {
214 let repo = InMemorySignerRepository::new();
215 let signer1 = create_test_signer("test".to_string());
216 let signer2 = create_test_signer("test2".to_string());
217
218 repo.create(signer1.clone()).await.unwrap();
219 repo.create(signer2).await.unwrap();
220
221 let signers = repo.list_all().await.unwrap();
222 assert_eq!(signers.len(), 2);
223 }
224
225 #[actix_web::test]
226 async fn test_update_nonexistent_signer() {
227 let repo = InMemorySignerRepository::new();
228 let signer = create_test_signer("test".to_string());
229
230 let result = repo.update("test2".to_string(), signer).await;
231 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
232 }
233
234 #[actix_web::test]
235 async fn test_get_nonexistent_relayer() {
236 let repo = InMemorySignerRepository::new();
237
238 let result = repo.get_by_id("test".to_string()).await;
239 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
240 }
241
242 #[actix_web::test]
243 async fn test_has_entries() {
244 let repo = InMemorySignerRepository::new();
245 assert!(!repo.has_entries().await.unwrap());
246
247 let signer = create_test_signer("test".to_string());
248
249 repo.create(signer.clone()).await.unwrap();
250 assert!(repo.has_entries().await.unwrap());
251 }
252
253 #[actix_web::test]
254 async fn test_drop_all_entries() {
255 let repo = InMemorySignerRepository::new();
256 let signer = create_test_signer("test".to_string());
257 repo.create(signer.clone()).await.unwrap();
258 assert!(repo.has_entries().await.unwrap());
259
260 repo.drop_all_entries().await.unwrap();
261 assert!(!repo.has_entries().await.unwrap());
262 }
263}