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 {} not found",
74 id
75 ))),
76 }
77 }
78
79 #[allow(clippy::map_entry)]
80 async fn update(
81 &self,
82 id: String,
83 signer: SignerRepoModel,
84 ) -> Result<SignerRepoModel, RepositoryError> {
85 let mut store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
86 Self::acquire_lock(&self.store).await?;
87 if !store.contains_key(&id) {
88 return Err(RepositoryError::NotFound(format!(
89 "Signer with ID {} not found",
90 id
91 )));
92 }
93 store.insert(id, signer.clone());
94 Ok(signer)
95 }
96
97 async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
98 let mut store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
99 Self::acquire_lock(&self.store).await?;
100 if !store.contains_key(&id) {
101 return Err(RepositoryError::NotFound(format!(
102 "Signer with ID {} not found",
103 id
104 )));
105 }
106 store.remove(&id);
107 Ok(())
108 }
109
110 async fn list_all(&self) -> Result<Vec<SignerRepoModel>, RepositoryError> {
111 let store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
112 Self::acquire_lock(&self.store).await?;
113 let signers: Vec<SignerRepoModel> = store.values().cloned().collect();
114 Ok(signers)
115 }
116
117 async fn list_paginated(
118 &self,
119 query: PaginationQuery,
120 ) -> Result<PaginatedResult<SignerRepoModel>, RepositoryError> {
121 let total = self.count().await?;
122 let start = ((query.page - 1) * query.per_page) as usize;
123 let items: Vec<SignerRepoModel> = self
124 .store
125 .lock()
126 .await
127 .values()
128 .skip(start)
129 .take(query.per_page as usize)
130 .cloned()
131 .collect();
132
133 Ok(PaginatedResult {
134 items,
135 total: total as u64,
136 page: query.page,
137 per_page: query.per_page,
138 })
139 }
140
141 async fn count(&self) -> Result<usize, RepositoryError> {
142 let store: MutexGuard<'_, HashMap<String, SignerRepoModel>> =
143 Self::acquire_lock(&self.store).await?;
144 let length = store.len();
145 Ok(length)
146 }
147
148 async fn has_entries(&self) -> Result<bool, RepositoryError> {
149 let store = Self::acquire_lock(&self.store).await?;
150 Ok(!store.is_empty())
151 }
152
153 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
154 let mut store = Self::acquire_lock(&self.store).await?;
155 store.clear();
156 Ok(())
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use secrets::SecretVec;
163
164 use crate::models::{LocalSignerConfigStorage, SignerConfigStorage};
165
166 use super::*;
167
168 fn create_test_signer(id: String) -> SignerRepoModel {
169 SignerRepoModel {
170 id: id.clone(),
171 config: SignerConfigStorage::Local(LocalSignerConfigStorage {
172 raw_key: SecretVec::zero(0),
173 }),
174 }
175 }
176
177 #[actix_web::test]
178 async fn test_new_repository_is_empty() {
179 let repo = InMemorySignerRepository::new();
180 assert_eq!(repo.count().await.unwrap(), 0);
181 }
182
183 #[actix_web::test]
184 async fn test_add_signer() {
185 let repo = InMemorySignerRepository::new();
186 let signer = create_test_signer("test".to_string());
187
188 repo.create(signer.clone()).await.unwrap();
189 assert_eq!(repo.count().await.unwrap(), 1);
190
191 let stored = repo.get_by_id("test".to_string()).await.unwrap();
192 assert_eq!(stored.id, signer.id);
193 }
194
195 #[actix_web::test]
196 async fn test_update_signer() {
197 let repo = InMemorySignerRepository::new();
198 let signer = create_test_signer("test".to_string());
199
200 repo.create(signer.clone()).await.unwrap();
202
203 let updated_signer = SignerRepoModel {
205 id: "test".to_string(),
206 config: SignerConfigStorage::Local(LocalSignerConfigStorage {
207 raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[2; 32])),
208 }),
209 };
210
211 let result = repo.update("test".to_string(), updated_signer).await;
212 assert!(result.is_ok());
213 }
214
215 #[actix_web::test]
216 async fn test_list_signers() {
217 let repo = InMemorySignerRepository::new();
218 let signer1 = create_test_signer("test".to_string());
219 let signer2 = create_test_signer("test2".to_string());
220
221 repo.create(signer1.clone()).await.unwrap();
222 repo.create(signer2).await.unwrap();
223
224 let signers = repo.list_all().await.unwrap();
225 assert_eq!(signers.len(), 2);
226 }
227
228 #[actix_web::test]
229 async fn test_update_nonexistent_signer() {
230 let repo = InMemorySignerRepository::new();
231 let signer = create_test_signer("test".to_string());
232
233 let result = repo.update("test2".to_string(), signer).await;
234 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
235 }
236
237 #[actix_web::test]
238 async fn test_get_nonexistent_relayer() {
239 let repo = InMemorySignerRepository::new();
240
241 let result = repo.get_by_id("test".to_string()).await;
242 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
243 }
244
245 #[actix_web::test]
246 async fn test_has_entries() {
247 let repo = InMemorySignerRepository::new();
248 assert!(!repo.has_entries().await.unwrap());
249
250 let signer = create_test_signer("test".to_string());
251
252 repo.create(signer.clone()).await.unwrap();
253 assert!(repo.has_entries().await.unwrap());
254 }
255
256 #[actix_web::test]
257 async fn test_drop_all_entries() {
258 let repo = InMemorySignerRepository::new();
259 let signer = create_test_signer("test".to_string());
260 repo.create(signer.clone()).await.unwrap();
261 assert!(repo.has_entries().await.unwrap());
262
263 repo.drop_all_entries().await.unwrap();
264 assert!(!repo.has_entries().await.unwrap());
265 }
266}