1use crate::models::RepositoryError;
7use log::{error, warn};
8use redis::RedisError;
9use serde::{Deserialize, Serialize};
10
11pub trait RedisRepository {
13 fn serialize_entity<T, F>(
14 &self,
15 entity: &T,
16 id_extractor: F,
17 entity_type: &str,
18 ) -> Result<String, RepositoryError>
19 where
20 T: Serialize,
21 F: Fn(&T) -> &str,
22 {
23 serde_json::to_string(entity).map_err(|e| {
24 let id = id_extractor(entity);
25 error!("Serialization failed for {} {}: {}", entity_type, id, e);
26 RepositoryError::InvalidData(format!(
27 "Failed to serialize {} {}: {}",
28 entity_type, id, e
29 ))
30 })
31 }
32
33 fn deserialize_entity<T>(
36 &self,
37 json: &str,
38 entity_id: &str,
39 entity_type: &str,
40 ) -> Result<T, RepositoryError>
41 where
42 T: for<'de> Deserialize<'de>,
43 {
44 serde_json::from_str(json).map_err(|e| {
45 error!(
46 "Deserialization failed for {} {}: {}",
47 entity_type, entity_id, e
48 );
49 RepositoryError::InvalidData(format!(
50 "Failed to deserialize {} {}: {} (JSON length: {})",
51 entity_type,
52 entity_id,
53 e,
54 json.len()
55 ))
56 })
57 }
58
59 fn map_redis_error(&self, error: RedisError, context: &str) -> RepositoryError {
61 warn!("Redis operation failed in context '{}': {}", context, error);
62
63 match error.kind() {
64 redis::ErrorKind::TypeError => RepositoryError::InvalidData(format!(
65 "Redis data type error in operation '{}': {}",
66 context, error
67 )),
68 redis::ErrorKind::AuthenticationFailed => {
69 RepositoryError::InvalidData("Redis authentication failed".to_string())
70 }
71 redis::ErrorKind::NoScriptError => RepositoryError::InvalidData(format!(
72 "Redis script error in operation '{}': {}",
73 context, error
74 )),
75 redis::ErrorKind::ReadOnly => RepositoryError::InvalidData(format!(
76 "Redis is read-only in operation '{}': {}",
77 context, error
78 )),
79 redis::ErrorKind::ExecAbortError => RepositoryError::InvalidData(format!(
80 "Redis transaction aborted in operation '{}': {}",
81 context, error
82 )),
83 redis::ErrorKind::BusyLoadingError => RepositoryError::InvalidData(format!(
84 "Redis is busy in operation '{}': {}",
85 context, error
86 )),
87 redis::ErrorKind::ExtensionError => RepositoryError::InvalidData(format!(
88 "Redis extension error in operation '{}': {}",
89 context, error
90 )),
91 _ => RepositoryError::Other(format!("Redis operation '{}' failed: {}", context, error)),
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use serde::{Deserialize, Serialize};
101
102 #[derive(Debug, Serialize, Deserialize, PartialEq)]
104 struct TestEntity {
105 id: String,
106 name: String,
107 value: i32,
108 }
109
110 #[derive(Debug, Serialize, Deserialize, PartialEq)]
111 struct SimpleEntity {
112 id: String,
113 }
114
115 struct TestRedisRepository;
117
118 impl RedisRepository for TestRedisRepository {}
119
120 impl TestRedisRepository {
121 fn new() -> Self {
122 TestRedisRepository
123 }
124 }
125
126 #[test]
127 fn test_serialize_entity_success() {
128 let repo = TestRedisRepository::new();
129 let entity = TestEntity {
130 id: "test-id".to_string(),
131 name: "test-name".to_string(),
132 value: 42,
133 };
134
135 let result = repo.serialize_entity(&entity, |e| &e.id, "TestEntity");
136
137 assert!(result.is_ok());
138 let json = result.unwrap();
139 assert!(json.contains("test-id"));
140 assert!(json.contains("test-name"));
141 assert!(json.contains("42"));
142 }
143
144 #[test]
145 fn test_serialize_entity_with_different_id_extractor() {
146 let repo = TestRedisRepository::new();
147 let entity = TestEntity {
148 id: "test-id".to_string(),
149 name: "test-name".to_string(),
150 value: 42,
151 };
152
153 let result = repo.serialize_entity(&entity, |e| &e.name, "TestEntity");
155
156 assert!(result.is_ok());
157 let json = result.unwrap();
158
159 assert!(json.contains("test-id"));
161 assert!(json.contains("test-name"));
162 assert!(json.contains("42"));
163 }
164
165 #[test]
166 fn test_serialize_entity_simple_struct() {
167 let repo = TestRedisRepository::new();
168 let entity = SimpleEntity {
169 id: "simple-id".to_string(),
170 };
171
172 let result = repo.serialize_entity(&entity, |e| &e.id, "SimpleEntity");
173
174 assert!(result.is_ok());
175 let json = result.unwrap();
176 assert!(json.contains("simple-id"));
177 }
178
179 #[test]
180 fn test_deserialize_entity_success() {
181 let repo = TestRedisRepository::new();
182 let json = r#"{"id":"test-id","name":"test-name","value":42}"#;
183
184 let result: Result<TestEntity, RepositoryError> =
185 repo.deserialize_entity(json, "test-id", "TestEntity");
186
187 assert!(result.is_ok());
188 let entity = result.unwrap();
189 assert_eq!(entity.id, "test-id");
190 assert_eq!(entity.name, "test-name");
191 assert_eq!(entity.value, 42);
192 }
193
194 #[test]
195 fn test_deserialize_entity_invalid_json() {
196 let repo = TestRedisRepository::new();
197 let invalid_json = r#"{"id":"test-id","name":"test-name","value":}"#; let result: Result<TestEntity, RepositoryError> =
200 repo.deserialize_entity(invalid_json, "test-id", "TestEntity");
201
202 assert!(result.is_err());
203 match result.unwrap_err() {
204 RepositoryError::InvalidData(msg) => {
205 assert!(msg.contains("Failed to deserialize TestEntity test-id"));
206 assert!(msg.contains("JSON length:"));
207 }
208 _ => panic!("Expected InvalidData error"),
209 }
210 }
211
212 #[test]
213 fn test_deserialize_entity_invalid_structure() {
214 let repo = TestRedisRepository::new();
215 let json = r#"{"wrongfield":"test-id"}"#;
216
217 let result: Result<TestEntity, RepositoryError> =
218 repo.deserialize_entity(json, "test-id", "TestEntity");
219
220 assert!(result.is_err());
221 match result.unwrap_err() {
222 RepositoryError::InvalidData(msg) => {
223 assert!(msg.contains("Failed to deserialize TestEntity test-id"));
224 }
225 _ => panic!("Expected InvalidData error"),
226 }
227 }
228
229 #[test]
230 fn test_map_redis_error_type_error() {
231 let repo = TestRedisRepository::new();
232 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Type error"));
233
234 let result = repo.map_redis_error(redis_error, "test_operation");
235
236 match result {
237 RepositoryError::InvalidData(msg) => {
238 assert!(msg.contains("Redis data type error"));
239 assert!(msg.contains("test_operation"));
240 }
241 _ => panic!("Expected InvalidData error"),
242 }
243 }
244
245 #[test]
246 fn test_map_redis_error_authentication_failed() {
247 let repo = TestRedisRepository::new();
248 let redis_error = RedisError::from((redis::ErrorKind::AuthenticationFailed, "Auth failed"));
249
250 let result = repo.map_redis_error(redis_error, "auth_operation");
251
252 match result {
253 RepositoryError::InvalidData(msg) => {
254 assert!(msg.contains("Redis authentication failed"));
255 }
256 _ => panic!("Expected InvalidData error"),
257 }
258 }
259
260 #[test]
261 fn test_map_redis_error_connection_error() {
262 let repo = TestRedisRepository::new();
263 let redis_error = RedisError::from((redis::ErrorKind::IoError, "Connection failed"));
264
265 let result = repo.map_redis_error(redis_error, "connection_operation");
266
267 match result {
268 RepositoryError::Other(msg) => {
269 assert!(msg.contains("Redis operation"));
270 assert!(msg.contains("connection_operation"));
271 }
272 _ => panic!("Expected Other error"),
273 }
274 }
275
276 #[test]
277 fn test_map_redis_error_no_script_error() {
278 let repo = TestRedisRepository::new();
279 let redis_error = RedisError::from((redis::ErrorKind::NoScriptError, "Script not found"));
280
281 let result = repo.map_redis_error(redis_error, "script_operation");
282
283 match result {
284 RepositoryError::InvalidData(msg) => {
285 assert!(msg.contains("Redis script error"));
286 assert!(msg.contains("script_operation"));
287 }
288 _ => panic!("Expected InvalidData error"),
289 }
290 }
291
292 #[test]
293 fn test_map_redis_error_read_only() {
294 let repo = TestRedisRepository::new();
295 let redis_error = RedisError::from((redis::ErrorKind::ReadOnly, "Read only"));
296
297 let result = repo.map_redis_error(redis_error, "write_operation");
298
299 match result {
300 RepositoryError::InvalidData(msg) => {
301 assert!(msg.contains("Redis is read-only"));
302 assert!(msg.contains("write_operation"));
303 }
304 _ => panic!("Expected InvalidData error"),
305 }
306 }
307
308 #[test]
309 fn test_map_redis_error_exec_abort_error() {
310 let repo = TestRedisRepository::new();
311 let redis_error =
312 RedisError::from((redis::ErrorKind::ExecAbortError, "Transaction aborted"));
313
314 let result = repo.map_redis_error(redis_error, "transaction_operation");
315
316 match result {
317 RepositoryError::InvalidData(msg) => {
318 assert!(msg.contains("Redis transaction aborted"));
319 assert!(msg.contains("transaction_operation"));
320 }
321 _ => panic!("Expected InvalidData error"),
322 }
323 }
324
325 #[test]
326 fn test_map_redis_error_busy_error() {
327 let repo = TestRedisRepository::new();
328 let redis_error = RedisError::from((redis::ErrorKind::BusyLoadingError, "Server busy"));
329
330 let result = repo.map_redis_error(redis_error, "busy_operation");
331
332 match result {
333 RepositoryError::InvalidData(msg) => {
334 assert!(msg.contains("Redis is busy"));
335 assert!(msg.contains("busy_operation"));
336 }
337 _ => panic!("Expected InvalidData error"),
338 }
339 }
340
341 #[test]
342 fn test_map_redis_error_extension_error() {
343 let repo = TestRedisRepository::new();
344 let redis_error = RedisError::from((redis::ErrorKind::ExtensionError, "Extension error"));
345
346 let result = repo.map_redis_error(redis_error, "extension_operation");
347
348 match result {
349 RepositoryError::InvalidData(msg) => {
350 assert!(msg.contains("Redis extension error"));
351 assert!(msg.contains("extension_operation"));
352 }
353 _ => panic!("Expected InvalidData error"),
354 }
355 }
356
357 #[test]
358 fn test_map_redis_error_context_propagation() {
359 let repo = TestRedisRepository::new();
360 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Type error"));
361 let context = "user_repository_get_operation";
362
363 let result = repo.map_redis_error(redis_error, context);
364
365 match result {
366 RepositoryError::InvalidData(msg) => {
367 assert!(msg.contains("Redis data type error"));
368 }
370 _ => panic!("Expected InvalidData error"),
371 }
372 }
373
374 #[test]
375 fn test_serialize_deserialize_roundtrip() {
376 let repo = TestRedisRepository::new();
377 let original = TestEntity {
378 id: "roundtrip-id".to_string(),
379 name: "roundtrip-name".to_string(),
380 value: 123,
381 };
382
383 let json = repo
385 .serialize_entity(&original, |e| &e.id, "TestEntity")
386 .unwrap();
387
388 let deserialized: TestEntity = repo
390 .deserialize_entity(&json, "roundtrip-id", "TestEntity")
391 .unwrap();
392
393 assert_eq!(original, deserialized);
395 }
396
397 #[test]
398 fn test_serialize_deserialize_unicode_content() {
399 let repo = TestRedisRepository::new();
400 let original = TestEntity {
401 id: "unicode-id".to_string(),
402 name: "测试名称 🚀".to_string(),
403 value: 456,
404 };
405
406 let json = repo
408 .serialize_entity(&original, |e| &e.id, "TestEntity")
409 .unwrap();
410
411 let deserialized: TestEntity = repo
413 .deserialize_entity(&json, "unicode-id", "TestEntity")
414 .unwrap();
415
416 assert_eq!(original, deserialized);
418 }
419
420 #[test]
421 fn test_serialize_entity_with_complex_data() {
422 let repo = TestRedisRepository::new();
423
424 #[derive(Serialize)]
425 struct ComplexEntity {
426 id: String,
427 nested: NestedData,
428 list: Vec<i32>,
429 }
430
431 #[derive(Serialize)]
432 struct NestedData {
433 field1: String,
434 field2: bool,
435 }
436
437 let complex_entity = ComplexEntity {
438 id: "complex-id".to_string(),
439 nested: NestedData {
440 field1: "nested-value".to_string(),
441 field2: true,
442 },
443 list: vec![1, 2, 3],
444 };
445
446 let result = repo.serialize_entity(&complex_entity, |e| &e.id, "ComplexEntity");
447
448 assert!(result.is_ok());
449 let json = result.unwrap();
450 assert!(json.contains("complex-id"));
451 assert!(json.contains("nested-value"));
452 assert!(json.contains("true"));
453 assert!(json.contains("[1,2,3]"));
454 }
455
456 #[test]
458 fn test_serialize_deserialize_u128_large_values() {
459 use crate::utils::{deserialize_optional_u128, serialize_optional_u128};
460
461 #[derive(Serialize, Deserialize, PartialEq, Debug)]
462 struct TestU128Entity {
463 id: String,
464 #[serde(
465 serialize_with = "serialize_optional_u128",
466 deserialize_with = "deserialize_optional_u128",
467 default
468 )]
469 gas_price: Option<u128>,
470 #[serde(
471 serialize_with = "serialize_optional_u128",
472 deserialize_with = "deserialize_optional_u128",
473 default
474 )]
475 max_fee_per_gas: Option<u128>,
476 }
477
478 let repo = TestRedisRepository::new();
479
480 let original = TestU128Entity {
482 id: "u128-test".to_string(),
483 gas_price: Some(u128::MAX), max_fee_per_gas: Some(999999999999999999999999999999999u128),
485 };
486
487 let json = repo
489 .serialize_entity(&original, |e| &e.id, "TestU128Entity")
490 .unwrap();
491
492 assert!(json.contains("\"340282366920938463463374607431768211455\""));
494 assert!(json.contains("\"999999999999999999999999999999999\""));
495 assert!(!json.contains("3.4028236692093846e+38"));
497
498 let deserialized: TestU128Entity = repo
500 .deserialize_entity(&json, "u128-test", "TestU128Entity")
501 .unwrap();
502
503 assert_eq!(original, deserialized);
505 assert_eq!(deserialized.gas_price, Some(u128::MAX));
506 assert_eq!(
507 deserialized.max_fee_per_gas,
508 Some(999999999999999999999999999999999u128)
509 );
510 }
511
512 #[test]
513 fn test_serialize_deserialize_u128_none_values() {
514 use crate::utils::{deserialize_optional_u128, serialize_optional_u128};
515
516 #[derive(Serialize, Deserialize, PartialEq, Debug)]
517 struct TestU128Entity {
518 id: String,
519 #[serde(
520 serialize_with = "serialize_optional_u128",
521 deserialize_with = "deserialize_optional_u128",
522 default
523 )]
524 gas_price: Option<u128>,
525 }
526
527 let repo = TestRedisRepository::new();
528
529 let original = TestU128Entity {
531 id: "u128-none-test".to_string(),
532 gas_price: None,
533 };
534
535 let json = repo
537 .serialize_entity(&original, |e| &e.id, "TestU128Entity")
538 .unwrap();
539
540 assert!(json.contains("null"));
542
543 let deserialized: TestU128Entity = repo
545 .deserialize_entity(&json, "u128-none-test", "TestU128Entity")
546 .unwrap();
547
548 assert_eq!(original, deserialized);
550 assert_eq!(deserialized.gas_price, None);
551 }
552}