1use crate::models::RepositoryError;
7use deadpool_redis::{Connection, Pool, PoolError, TimeoutType};
8use redis::RedisError;
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use tracing::{error, warn};
12
13pub trait RedisRepository {
15 fn serialize_entity<T, F>(
16 &self,
17 entity: &T,
18 id_extractor: F,
19 entity_type: &str,
20 ) -> Result<String, RepositoryError>
21 where
22 T: Serialize,
23 F: Fn(&T) -> &str,
24 {
25 serde_json::to_string(entity).map_err(|e| {
26 let id = id_extractor(entity);
27 error!(entity_type = %entity_type, id = %id, error = %e, "serialization failed");
28 RepositoryError::InvalidData(format!("Failed to serialize {entity_type} {id}: {e}"))
29 })
30 }
31
32 fn deserialize_entity<T>(
35 &self,
36 json: &str,
37 entity_id: &str,
38 entity_type: &str,
39 ) -> Result<T, RepositoryError>
40 where
41 T: for<'de> Deserialize<'de>,
42 {
43 serde_json::from_str(json).map_err(|e| {
44 error!(entity_type = %entity_type, entity_id = %entity_id, error = %e, "deserialization failed");
45 RepositoryError::InvalidData(format!(
46 "Failed to deserialize {} {}: {} (JSON length: {})",
47 entity_type,
48 entity_id,
49 e,
50 json.len()
51 ))
52 })
53 }
54
55 fn map_redis_error(&self, error: RedisError, context: &str) -> RepositoryError {
57 warn!(context = %context, error = %error, "redis operation failed");
58
59 match error.kind() {
60 redis::ErrorKind::TypeError => RepositoryError::InvalidData(format!(
61 "Redis data type error in operation '{context}': {error}"
62 )),
63 redis::ErrorKind::AuthenticationFailed => {
64 RepositoryError::InvalidData("Redis authentication failed".to_string())
65 }
66 redis::ErrorKind::NoScriptError => RepositoryError::InvalidData(format!(
67 "Redis script error in operation '{context}': {error}"
68 )),
69 redis::ErrorKind::ReadOnly => RepositoryError::InvalidData(format!(
70 "Redis is read-only in operation '{context}': {error}"
71 )),
72 redis::ErrorKind::ExecAbortError => RepositoryError::InvalidData(format!(
73 "Redis transaction aborted in operation '{context}': {error}"
74 )),
75 redis::ErrorKind::BusyLoadingError => RepositoryError::InvalidData(format!(
76 "Redis is busy in operation '{context}': {error}"
77 )),
78 redis::ErrorKind::ExtensionError => RepositoryError::InvalidData(format!(
79 "Redis extension error in operation '{context}': {error}"
80 )),
81 _ => RepositoryError::Other(format!("Redis operation '{context}' failed: {error}")),
83 }
84 }
85
86 fn map_pool_error(&self, error: PoolError, context: &str) -> RepositoryError {
88 error!(context = %context, error = %error, "redis pool operation failed");
89
90 match error {
91 PoolError::Timeout(timeout) => {
92 let detail = match timeout {
93 TimeoutType::Wait => "waiting for an available connection",
94 TimeoutType::Create => "creating a new connection",
95 TimeoutType::Recycle => "recycling a connection",
96 };
97 RepositoryError::ConnectionError(format!(
98 "Redis pool timeout while {detail} in operation '{context}'"
99 ))
100 }
101 PoolError::Backend(redis_err) => self.map_redis_error(redis_err, context),
102 PoolError::Closed => {
103 RepositoryError::ConnectionError("Redis pool is closed".to_string())
104 }
105 PoolError::NoRuntimeSpecified => {
106 RepositoryError::ConnectionError("Redis pool has no runtime specified".to_string())
107 }
108 other => RepositoryError::ConnectionError(format!(
109 "Redis pool error in operation '{context}': {other}"
110 )),
111 }
112 }
113
114 #[allow(async_fn_in_trait)]
125 async fn get_connection(
126 &self,
127 pool: &Arc<Pool>,
128 context: &str,
129 ) -> Result<Connection, RepositoryError> {
130 pool.get()
131 .await
132 .map_err(|e| self.map_pool_error(e, context))
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use serde::{Deserialize, Serialize};
140
141 #[derive(Debug, Serialize, Deserialize, PartialEq)]
143 struct TestEntity {
144 id: String,
145 name: String,
146 value: i32,
147 }
148
149 #[derive(Debug, Serialize, Deserialize, PartialEq)]
150 struct SimpleEntity {
151 id: String,
152 }
153
154 struct TestRedisRepository;
156
157 impl RedisRepository for TestRedisRepository {}
158
159 impl TestRedisRepository {
160 fn new() -> Self {
161 TestRedisRepository
162 }
163 }
164
165 #[test]
166 fn test_serialize_entity_success() {
167 let repo = TestRedisRepository::new();
168 let entity = TestEntity {
169 id: "test-id".to_string(),
170 name: "test-name".to_string(),
171 value: 42,
172 };
173
174 let result = repo.serialize_entity(&entity, |e| &e.id, "TestEntity");
175
176 assert!(result.is_ok());
177 let json = result.unwrap();
178 assert!(json.contains("test-id"));
179 assert!(json.contains("test-name"));
180 assert!(json.contains("42"));
181 }
182
183 #[test]
184 fn test_serialize_entity_with_different_id_extractor() {
185 let repo = TestRedisRepository::new();
186 let entity = TestEntity {
187 id: "test-id".to_string(),
188 name: "test-name".to_string(),
189 value: 42,
190 };
191
192 let result = repo.serialize_entity(&entity, |e| &e.name, "TestEntity");
194
195 assert!(result.is_ok());
196 let json = result.unwrap();
197
198 assert!(json.contains("test-id"));
200 assert!(json.contains("test-name"));
201 assert!(json.contains("42"));
202 }
203
204 #[test]
205 fn test_serialize_entity_simple_struct() {
206 let repo = TestRedisRepository::new();
207 let entity = SimpleEntity {
208 id: "simple-id".to_string(),
209 };
210
211 let result = repo.serialize_entity(&entity, |e| &e.id, "SimpleEntity");
212
213 assert!(result.is_ok());
214 let json = result.unwrap();
215 assert!(json.contains("simple-id"));
216 }
217
218 #[test]
219 fn test_deserialize_entity_success() {
220 let repo = TestRedisRepository::new();
221 let json = r#"{"id":"test-id","name":"test-name","value":42}"#;
222
223 let result: Result<TestEntity, RepositoryError> =
224 repo.deserialize_entity(json, "test-id", "TestEntity");
225
226 assert!(result.is_ok());
227 let entity = result.unwrap();
228 assert_eq!(entity.id, "test-id");
229 assert_eq!(entity.name, "test-name");
230 assert_eq!(entity.value, 42);
231 }
232
233 #[test]
234 fn test_deserialize_entity_invalid_json() {
235 let repo = TestRedisRepository::new();
236 let invalid_json = r#"{"id":"test-id","name":"test-name","value":}"#; let result: Result<TestEntity, RepositoryError> =
239 repo.deserialize_entity(invalid_json, "test-id", "TestEntity");
240
241 assert!(result.is_err());
242 match result.unwrap_err() {
243 RepositoryError::InvalidData(msg) => {
244 assert!(msg.contains("Failed to deserialize TestEntity test-id"));
245 assert!(msg.contains("JSON length:"));
246 }
247 _ => panic!("Expected InvalidData error"),
248 }
249 }
250
251 #[test]
252 fn test_deserialize_entity_invalid_structure() {
253 let repo = TestRedisRepository::new();
254 let json = r#"{"wrongfield":"test-id"}"#;
255
256 let result: Result<TestEntity, RepositoryError> =
257 repo.deserialize_entity(json, "test-id", "TestEntity");
258
259 assert!(result.is_err());
260 match result.unwrap_err() {
261 RepositoryError::InvalidData(msg) => {
262 assert!(msg.contains("Failed to deserialize TestEntity test-id"));
263 }
264 _ => panic!("Expected InvalidData error"),
265 }
266 }
267
268 #[test]
269 fn test_map_redis_error_type_error() {
270 let repo = TestRedisRepository::new();
271 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Type error"));
272
273 let result = repo.map_redis_error(redis_error, "test_operation");
274
275 match result {
276 RepositoryError::InvalidData(msg) => {
277 assert!(msg.contains("Redis data type error"));
278 assert!(msg.contains("test_operation"));
279 }
280 _ => panic!("Expected InvalidData error"),
281 }
282 }
283
284 #[test]
285 fn test_map_redis_error_authentication_failed() {
286 let repo = TestRedisRepository::new();
287 let redis_error = RedisError::from((redis::ErrorKind::AuthenticationFailed, "Auth failed"));
288
289 let result = repo.map_redis_error(redis_error, "auth_operation");
290
291 match result {
292 RepositoryError::InvalidData(msg) => {
293 assert!(msg.contains("Redis authentication failed"));
294 }
295 _ => panic!("Expected InvalidData error"),
296 }
297 }
298
299 #[test]
300 fn test_map_redis_error_connection_error() {
301 let repo = TestRedisRepository::new();
302 let redis_error = RedisError::from((redis::ErrorKind::IoError, "Connection failed"));
303
304 let result = repo.map_redis_error(redis_error, "connection_operation");
305
306 match result {
307 RepositoryError::Other(msg) => {
308 assert!(msg.contains("Redis operation"));
309 assert!(msg.contains("connection_operation"));
310 }
311 _ => panic!("Expected Other error"),
312 }
313 }
314
315 #[test]
316 fn test_map_redis_error_no_script_error() {
317 let repo = TestRedisRepository::new();
318 let redis_error = RedisError::from((redis::ErrorKind::NoScriptError, "Script not found"));
319
320 let result = repo.map_redis_error(redis_error, "script_operation");
321
322 match result {
323 RepositoryError::InvalidData(msg) => {
324 assert!(msg.contains("Redis script error"));
325 assert!(msg.contains("script_operation"));
326 }
327 _ => panic!("Expected InvalidData error"),
328 }
329 }
330
331 #[test]
332 fn test_map_redis_error_read_only() {
333 let repo = TestRedisRepository::new();
334 let redis_error = RedisError::from((redis::ErrorKind::ReadOnly, "Read only"));
335
336 let result = repo.map_redis_error(redis_error, "write_operation");
337
338 match result {
339 RepositoryError::InvalidData(msg) => {
340 assert!(msg.contains("Redis is read-only"));
341 assert!(msg.contains("write_operation"));
342 }
343 _ => panic!("Expected InvalidData error"),
344 }
345 }
346
347 #[test]
348 fn test_map_redis_error_exec_abort_error() {
349 let repo = TestRedisRepository::new();
350 let redis_error =
351 RedisError::from((redis::ErrorKind::ExecAbortError, "Transaction aborted"));
352
353 let result = repo.map_redis_error(redis_error, "transaction_operation");
354
355 match result {
356 RepositoryError::InvalidData(msg) => {
357 assert!(msg.contains("Redis transaction aborted"));
358 assert!(msg.contains("transaction_operation"));
359 }
360 _ => panic!("Expected InvalidData error"),
361 }
362 }
363
364 #[test]
365 fn test_map_redis_error_busy_error() {
366 let repo = TestRedisRepository::new();
367 let redis_error = RedisError::from((redis::ErrorKind::BusyLoadingError, "Server busy"));
368
369 let result = repo.map_redis_error(redis_error, "busy_operation");
370
371 match result {
372 RepositoryError::InvalidData(msg) => {
373 assert!(msg.contains("Redis is busy"));
374 assert!(msg.contains("busy_operation"));
375 }
376 _ => panic!("Expected InvalidData error"),
377 }
378 }
379
380 #[test]
381 fn test_map_redis_error_extension_error() {
382 let repo = TestRedisRepository::new();
383 let redis_error = RedisError::from((redis::ErrorKind::ExtensionError, "Extension error"));
384
385 let result = repo.map_redis_error(redis_error, "extension_operation");
386
387 match result {
388 RepositoryError::InvalidData(msg) => {
389 assert!(msg.contains("Redis extension error"));
390 assert!(msg.contains("extension_operation"));
391 }
392 _ => panic!("Expected InvalidData error"),
393 }
394 }
395
396 #[test]
397 fn test_map_redis_error_context_propagation() {
398 let repo = TestRedisRepository::new();
399 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Type error"));
400 let context = "user_repository_get_operation";
401
402 let result = repo.map_redis_error(redis_error, context);
403
404 match result {
405 RepositoryError::InvalidData(msg) => {
406 assert!(msg.contains("Redis data type error"));
407 }
409 _ => panic!("Expected InvalidData error"),
410 }
411 }
412
413 #[test]
414 fn test_serialize_deserialize_roundtrip() {
415 let repo = TestRedisRepository::new();
416 let original = TestEntity {
417 id: "roundtrip-id".to_string(),
418 name: "roundtrip-name".to_string(),
419 value: 123,
420 };
421
422 let json = repo
424 .serialize_entity(&original, |e| &e.id, "TestEntity")
425 .unwrap();
426
427 let deserialized: TestEntity = repo
429 .deserialize_entity(&json, "roundtrip-id", "TestEntity")
430 .unwrap();
431
432 assert_eq!(original, deserialized);
434 }
435
436 #[test]
437 fn test_serialize_deserialize_unicode_content() {
438 let repo = TestRedisRepository::new();
439 let original = TestEntity {
440 id: "unicode-id".to_string(),
441 name: "测试名称 🚀".to_string(),
442 value: 456,
443 };
444
445 let json = repo
447 .serialize_entity(&original, |e| &e.id, "TestEntity")
448 .unwrap();
449
450 let deserialized: TestEntity = repo
452 .deserialize_entity(&json, "unicode-id", "TestEntity")
453 .unwrap();
454
455 assert_eq!(original, deserialized);
457 }
458
459 #[test]
460 fn test_serialize_entity_with_complex_data() {
461 let repo = TestRedisRepository::new();
462
463 #[derive(Serialize)]
464 struct ComplexEntity {
465 id: String,
466 nested: NestedData,
467 list: Vec<i32>,
468 }
469
470 #[derive(Serialize)]
471 struct NestedData {
472 field1: String,
473 field2: bool,
474 }
475
476 let complex_entity = ComplexEntity {
477 id: "complex-id".to_string(),
478 nested: NestedData {
479 field1: "nested-value".to_string(),
480 field2: true,
481 },
482 list: vec![1, 2, 3],
483 };
484
485 let result = repo.serialize_entity(&complex_entity, |e| &e.id, "ComplexEntity");
486
487 assert!(result.is_ok());
488 let json = result.unwrap();
489 assert!(json.contains("complex-id"));
490 assert!(json.contains("nested-value"));
491 assert!(json.contains("true"));
492 assert!(json.contains("[1,2,3]"));
493 }
494
495 #[test]
497 fn test_serialize_deserialize_u128_large_values() {
498 use crate::utils::{deserialize_optional_u128, serialize_optional_u128};
499
500 #[derive(Serialize, Deserialize, PartialEq, Debug)]
501 struct TestU128Entity {
502 id: String,
503 #[serde(
504 serialize_with = "serialize_optional_u128",
505 deserialize_with = "deserialize_optional_u128",
506 default
507 )]
508 gas_price: Option<u128>,
509 #[serde(
510 serialize_with = "serialize_optional_u128",
511 deserialize_with = "deserialize_optional_u128",
512 default
513 )]
514 max_fee_per_gas: Option<u128>,
515 }
516
517 let repo = TestRedisRepository::new();
518
519 let original = TestU128Entity {
521 id: "u128-test".to_string(),
522 gas_price: Some(u128::MAX), max_fee_per_gas: Some(999999999999999999999999999999999u128),
524 };
525
526 let json = repo
528 .serialize_entity(&original, |e| &e.id, "TestU128Entity")
529 .unwrap();
530
531 assert!(json.contains("\"340282366920938463463374607431768211455\""));
533 assert!(json.contains("\"999999999999999999999999999999999\""));
534 assert!(!json.contains("3.4028236692093846e+38"));
536
537 let deserialized: TestU128Entity = repo
539 .deserialize_entity(&json, "u128-test", "TestU128Entity")
540 .unwrap();
541
542 assert_eq!(original, deserialized);
544 assert_eq!(deserialized.gas_price, Some(u128::MAX));
545 assert_eq!(
546 deserialized.max_fee_per_gas,
547 Some(999999999999999999999999999999999u128)
548 );
549 }
550
551 #[test]
552 fn test_serialize_deserialize_u128_none_values() {
553 use crate::utils::{deserialize_optional_u128, serialize_optional_u128};
554
555 #[derive(Serialize, Deserialize, PartialEq, Debug)]
556 struct TestU128Entity {
557 id: String,
558 #[serde(
559 serialize_with = "serialize_optional_u128",
560 deserialize_with = "deserialize_optional_u128",
561 default
562 )]
563 gas_price: Option<u128>,
564 }
565
566 let repo = TestRedisRepository::new();
567
568 let original = TestU128Entity {
570 id: "u128-none-test".to_string(),
571 gas_price: None,
572 };
573
574 let json = repo
576 .serialize_entity(&original, |e| &e.id, "TestU128Entity")
577 .unwrap();
578
579 assert!(json.contains("null"));
581
582 let deserialized: TestU128Entity = repo
584 .deserialize_entity(&json, "u128-none-test", "TestU128Entity")
585 .unwrap();
586
587 assert_eq!(original, deserialized);
589 assert_eq!(deserialized.gas_price, None);
590 }
591
592 #[test]
593 fn test_map_pool_error_timeout_wait() {
594 let repo = TestRedisRepository::new();
595 let timeout_error = PoolError::Timeout(TimeoutType::Wait);
596
597 let result = repo.map_pool_error(timeout_error, "test_operation");
598
599 match result {
600 RepositoryError::ConnectionError(msg) => {
601 assert!(msg.contains("Redis pool timeout"));
602 assert!(msg.contains("waiting for an available connection"));
603 assert!(msg.contains("test_operation"));
604 }
605 _ => panic!("Expected ConnectionError"),
606 }
607 }
608
609 #[test]
610 fn test_map_pool_error_timeout_create() {
611 let repo = TestRedisRepository::new();
612 let timeout_error = PoolError::Timeout(TimeoutType::Create);
613
614 let result = repo.map_pool_error(timeout_error, "create_operation");
615
616 match result {
617 RepositoryError::ConnectionError(msg) => {
618 assert!(msg.contains("Redis pool timeout"));
619 assert!(msg.contains("creating a new connection"));
620 assert!(msg.contains("create_operation"));
621 }
622 _ => panic!("Expected ConnectionError"),
623 }
624 }
625
626 #[test]
627 fn test_map_pool_error_timeout_recycle() {
628 let repo = TestRedisRepository::new();
629 let timeout_error = PoolError::Timeout(TimeoutType::Recycle);
630
631 let result = repo.map_pool_error(timeout_error, "recycle_operation");
632
633 match result {
634 RepositoryError::ConnectionError(msg) => {
635 assert!(msg.contains("Redis pool timeout"));
636 assert!(msg.contains("recycling a connection"));
637 assert!(msg.contains("recycle_operation"));
638 }
639 _ => panic!("Expected ConnectionError"),
640 }
641 }
642
643 #[test]
644 fn test_map_pool_error_backend() {
645 let repo = TestRedisRepository::new();
646 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Backend error"));
647 let pool_error = PoolError::Backend(redis_error);
648
649 let result = repo.map_pool_error(pool_error, "backend_operation");
650
651 match result {
653 RepositoryError::InvalidData(msg) => {
654 assert!(msg.contains("Redis data type error"));
655 assert!(msg.contains("backend_operation"));
656 }
657 _ => panic!("Expected InvalidData error from map_redis_error"),
658 }
659 }
660
661 #[test]
662 fn test_map_pool_error_closed() {
663 let repo = TestRedisRepository::new();
664 let pool_error = PoolError::Closed;
665
666 let result = repo.map_pool_error(pool_error, "closed_operation");
667
668 match result {
669 RepositoryError::ConnectionError(msg) => {
670 assert_eq!(msg, "Redis pool is closed");
671 }
672 _ => panic!("Expected ConnectionError"),
673 }
674 }
675
676 #[test]
677 fn test_map_pool_error_no_runtime() {
678 let repo = TestRedisRepository::new();
679 let pool_error = PoolError::NoRuntimeSpecified;
680
681 let result = repo.map_pool_error(pool_error, "runtime_operation");
682
683 match result {
684 RepositoryError::ConnectionError(msg) => {
685 assert_eq!(msg, "Redis pool has no runtime specified");
686 }
687 _ => panic!("Expected ConnectionError"),
688 }
689 }
690
691 #[test]
692 fn test_map_redis_error_empty_context() {
693 let repo = TestRedisRepository::new();
694 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Type error"));
695
696 let result = repo.map_redis_error(redis_error, "");
697
698 match result {
699 RepositoryError::InvalidData(msg) => {
700 assert!(msg.contains("Redis data type error"));
701 }
702 _ => panic!("Expected InvalidData error"),
703 }
704 }
705
706 #[test]
707 fn test_map_pool_error_empty_context() {
708 let repo = TestRedisRepository::new();
709 let pool_error = PoolError::Closed;
710
711 let result = repo.map_pool_error(pool_error, "");
712
713 match result {
714 RepositoryError::ConnectionError(msg) => {
715 assert_eq!(msg, "Redis pool is closed");
716 }
717 _ => panic!("Expected ConnectionError"),
718 }
719 }
720
721 #[test]
722 fn test_serialize_entity_empty_id() {
723 let repo = TestRedisRepository::new();
724 let entity = TestEntity {
725 id: "".to_string(),
726 name: "test-name".to_string(),
727 value: 42,
728 };
729
730 let result = repo.serialize_entity(&entity, |e| &e.id, "TestEntity");
731
732 assert!(result.is_ok());
733 let json = result.unwrap();
734 assert!(json.contains("\"id\":\"\""));
735 }
736
737 #[test]
738 fn test_deserialize_entity_empty_json() {
739 let repo = TestRedisRepository::new();
740
741 let result: Result<TestEntity, RepositoryError> =
742 repo.deserialize_entity("", "test-id", "TestEntity");
743
744 assert!(result.is_err());
745 match result.unwrap_err() {
746 RepositoryError::InvalidData(msg) => {
747 assert!(msg.contains("Failed to deserialize"));
748 assert!(msg.contains("JSON length: 0"));
749 }
750 _ => panic!("Expected InvalidData error"),
751 }
752 }
753
754 #[test]
755 fn test_deserialize_entity_malformed_json() {
756 let repo = TestRedisRepository::new();
757 let malformed_json = r#"{"id":"test-id","name":"test-name","value":}"#;
758
759 let result: Result<TestEntity, RepositoryError> =
760 repo.deserialize_entity(malformed_json, "test-id", "TestEntity");
761
762 assert!(result.is_err());
763 match result.unwrap_err() {
764 RepositoryError::InvalidData(msg) => {
765 assert!(msg.contains("Failed to deserialize"));
766 assert!(msg.contains("test-id"));
767 }
768 _ => panic!("Expected InvalidData error"),
769 }
770 }
771
772 #[test]
773 fn test_serialize_entity_with_special_characters() {
774 let repo = TestRedisRepository::new();
775 let entity = TestEntity {
776 id: "test-id".to_string(),
777 name: "test\"name\nwith\tspecial\rchars".to_string(),
778 value: 42,
779 };
780
781 let result = repo.serialize_entity(&entity, |e| &e.id, "TestEntity");
782
783 assert!(result.is_ok());
784 let json = result.unwrap();
785 assert!(json.contains("test-id"));
787 let deserialized: TestEntity = repo
789 .deserialize_entity(&json, "test-id", "TestEntity")
790 .unwrap();
791 assert_eq!(deserialized.name, entity.name);
792 }
793
794 #[test]
795 fn test_serialize_entity_with_numeric_id() {
796 let repo = TestRedisRepository::new();
797 #[derive(Serialize)]
798 struct NumericIdEntity {
799 id: i32,
800 name: String,
801 }
802
803 let entity = NumericIdEntity {
804 id: 12345,
805 name: "test".to_string(),
806 };
807
808 let result = repo.serialize_entity(&entity, |_| "numeric-id", "NumericIdEntity");
810
811 assert!(result.is_ok());
812 let json = result.unwrap();
813 assert!(json.contains("12345"));
814 assert!(json.contains("test")); }
816
817 #[test]
818 fn test_map_redis_error_all_error_kinds() {
819 let repo = TestRedisRepository::new();
820 let error_kinds = vec![
821 (redis::ErrorKind::TypeError, "TypeError"),
822 (
823 redis::ErrorKind::AuthenticationFailed,
824 "AuthenticationFailed",
825 ),
826 (redis::ErrorKind::NoScriptError, "NoScriptError"),
827 (redis::ErrorKind::ReadOnly, "ReadOnly"),
828 (redis::ErrorKind::ExecAbortError, "ExecAbortError"),
829 (redis::ErrorKind::BusyLoadingError, "BusyLoadingError"),
830 (redis::ErrorKind::ExtensionError, "ExtensionError"),
831 (redis::ErrorKind::IoError, "IoError"),
832 (redis::ErrorKind::ClientError, "ClientError"),
833 ];
834
835 for (kind, expected_type) in error_kinds {
836 let redis_error = RedisError::from((kind, "test error"));
837 let result = repo.map_redis_error(redis_error, "test_op");
838
839 match result {
840 RepositoryError::InvalidData(_)
841 if expected_type != "IoError" && expected_type != "ClientError" =>
842 {
843 }
845 RepositoryError::Other(_)
846 if expected_type == "IoError" || expected_type == "ClientError" =>
847 {
848 }
850 _ => panic!("Unexpected error type for {kind:?}"),
851 }
852 }
853 }
854
855 #[test]
856 fn test_serialize_entity_error_message_includes_id() {
857 let repo = TestRedisRepository::new();
858 let entity = TestEntity {
861 id: "error-test-id".to_string(),
862 name: "test-name".to_string(),
863 value: 42,
864 };
865
866 let result = repo.serialize_entity(&entity, |e| &e.id, "TestEntity");
869 assert!(result.is_ok());
870
871 }
874
875 #[test]
876 fn test_deserialize_entity_error_message_includes_length() {
877 let repo = TestRedisRepository::new();
878 let short_json = r#"{"id":"test"}"#;
879
880 let result: Result<TestEntity, RepositoryError> =
882 repo.deserialize_entity(short_json, "test-id", "TestEntity");
883 assert!(result.is_err());
884 if let RepositoryError::InvalidData(msg) = result.unwrap_err() {
885 assert!(msg.contains("JSON length:"));
886 }
887
888 let long_json_str = format!(r#"{{"id":"test","name":"{}"}}"#, "a".repeat(1000));
890 let result: Result<TestEntity, RepositoryError> =
891 repo.deserialize_entity(&long_json_str, "test-id", "TestEntity");
892 assert!(result.is_err());
893 if let RepositoryError::InvalidData(msg) = result.unwrap_err() {
894 assert!(msg.contains("JSON length:"));
895 assert!(msg.len() > 20); }
898 }
899
900 #[test]
901 fn test_map_pool_error_context_in_error_message() {
902 let repo = TestRedisRepository::new();
903 let contexts = vec!["get_by_id", "create", "update", "delete", "list_all"];
904
905 for context in contexts {
906 let timeout_error = PoolError::Timeout(TimeoutType::Wait);
908 let timeout_result = repo.map_pool_error(timeout_error, context);
909
910 match timeout_result {
911 RepositoryError::ConnectionError(msg) => {
912 assert!(
913 msg.contains(context),
914 "Context '{context}' should appear in error message"
915 );
916 }
917 _ => panic!("Expected ConnectionError"),
918 }
919 }
920 }
921
922 #[test]
923 fn test_serialize_entity_with_null_values() {
924 #[derive(Serialize, Deserialize, PartialEq, Debug)]
925 struct NullableEntity {
926 id: String,
927 optional_field: Option<String>,
928 }
929
930 let repo = TestRedisRepository::new();
931 let entity = NullableEntity {
932 id: "null-test".to_string(),
933 optional_field: None,
934 };
935
936 let json = repo
937 .serialize_entity(&entity, |e| &e.id, "NullableEntity")
938 .unwrap();
939
940 assert!(json.contains("null"));
941 assert!(json.contains("null-test"));
942
943 let deserialized: NullableEntity = repo
945 .deserialize_entity(&json, "null-test", "NullableEntity")
946 .unwrap();
947 assert_eq!(entity, deserialized);
948 }
949
950 #[test]
951 fn test_serialize_entity_with_empty_strings() {
952 let repo = TestRedisRepository::new();
953 let entity = TestEntity {
954 id: "empty-test".to_string(),
955 name: "".to_string(),
956 value: 0,
957 };
958
959 let json = repo
960 .serialize_entity(&entity, |e| &e.id, "TestEntity")
961 .unwrap();
962
963 assert!(json.contains("\"name\":\"\""));
964 assert!(json.contains("\"value\":0"));
965
966 let deserialized: TestEntity = repo
968 .deserialize_entity(&json, "empty-test", "TestEntity")
969 .unwrap();
970 assert_eq!(entity, deserialized);
971 }
972
973 #[test]
974 fn test_map_redis_error_different_contexts() {
975 let repo = TestRedisRepository::new();
976 let contexts = vec![
977 "short",
978 "very_long_context_name_that_might_be_used_in_real_world_scenarios",
979 "context-with-dashes",
980 "context_with_underscores",
981 "context.with.dots",
982 ];
983
984 for context in contexts {
985 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Type error"));
986 let result = repo.map_redis_error(redis_error, context);
987 match result {
988 RepositoryError::InvalidData(msg) => {
989 assert!(msg.contains("Redis data type error"));
990 }
991 _ => panic!("Expected InvalidData error"),
992 }
993 }
994 }
995}