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