Skip to main content

shardline_cache/
error.rs

1use std::num::TryFromIntError;
2
3use thiserror::Error;
4
5/// Reconstruction cache adapter failure.
6#[derive(Debug, Error)]
7pub enum ReconstructionCacheError {
8    /// The configured Redis URL was empty.
9    #[error("reconstruction cache redis url must not be empty")]
10    EmptyRedisUrl,
11    /// Only one half of a Redis mTLS identity was configured.
12    #[error("redis TLS client certificate and key must be configured together")]
13    IncompleteRedisTlsClientIdentity,
14    /// Redis client initialization or operations failed.
15    #[error("reconstruction cache redis operation failed")]
16    Redis(#[from] redis::RedisError),
17    /// Numeric conversion exceeded supported bounds.
18    #[error("reconstruction cache numeric conversion exceeded supported bounds")]
19    NumericConversion(#[from] TryFromIntError),
20    /// The adapter reported a generic operational failure.
21    #[error("reconstruction cache adapter operation failed")]
22    Operation,
23}
24
25#[cfg(test)]
26mod tests {
27    use std::error::Error as StdError;
28    use std::mem::discriminant;
29
30    use super::*;
31
32    #[allow(clippy::unwrap_used, clippy::shadow_unrelated)]
33    #[test]
34    fn error_source_returns_inner_error() {
35        // EmptyRedisUrl has no source
36        assert!(ReconstructionCacheError::EmptyRedisUrl.source().is_none());
37
38        // Operation has no source
39        assert!(ReconstructionCacheError::Operation.source().is_none());
40        assert!(
41            ReconstructionCacheError::IncompleteRedisTlsClientIdentity
42                .source()
43                .is_none()
44        );
45
46        // Redis wraps a redis::RedisError
47        let redis_err = redis::RedisError::from((redis::ErrorKind::Io, "inner source"));
48        let err = ReconstructionCacheError::Redis(redis_err);
49        let source = err.source();
50        assert!(source.is_some());
51        assert!(
52            source
53                .unwrap()
54                .downcast_ref::<redis::RedisError>()
55                .is_some()
56        );
57
58        // NumericConversion wraps a TryFromIntError
59        let int_err = i64::try_from(u64::MAX).unwrap_err();
60        let err = ReconstructionCacheError::NumericConversion(int_err);
61        let source = err.source();
62        assert!(source.is_some());
63        assert!(source.unwrap().downcast_ref::<TryFromIntError>().is_some());
64    }
65
66    #[test]
67    fn display_empty_redis_url() {
68        let err = ReconstructionCacheError::EmptyRedisUrl;
69        assert_eq!(
70            err.to_string(),
71            "reconstruction cache redis url must not be empty"
72        );
73    }
74
75    #[test]
76    fn display_operation() {
77        let err = ReconstructionCacheError::Operation;
78        assert_eq!(
79            err.to_string(),
80            "reconstruction cache adapter operation failed"
81        );
82    }
83
84    #[test]
85    fn display_incomplete_redis_tls_client_identity() {
86        let err = ReconstructionCacheError::IncompleteRedisTlsClientIdentity;
87        assert_eq!(
88            err.to_string(),
89            "redis TLS client certificate and key must be configured together"
90        );
91    }
92
93    #[test]
94    fn display_redis() {
95        let redis_err = redis::RedisError::from((redis::ErrorKind::Io, "test error"));
96        let err = ReconstructionCacheError::Redis(redis_err);
97        assert_eq!(
98            err.to_string(),
99            "reconstruction cache redis operation failed"
100        );
101    }
102
103    #[allow(clippy::unwrap_used)]
104    #[test]
105    fn display_numeric_conversion() {
106        let int_err = i64::try_from(u64::MAX).unwrap_err();
107        let err = ReconstructionCacheError::NumericConversion(int_err);
108        assert_eq!(
109            err.to_string(),
110            "reconstruction cache numeric conversion exceeded supported bounds"
111        );
112    }
113
114    #[test]
115    fn from_redis_error() {
116        let redis_err = redis::RedisError::from((redis::ErrorKind::Io, "test error"));
117        let err: ReconstructionCacheError = redis_err.into();
118        assert!(matches!(err, ReconstructionCacheError::Redis(_)));
119    }
120
121    #[allow(clippy::unwrap_used)]
122    #[test]
123    fn from_try_from_int_error() {
124        let int_err = i64::try_from(u64::MAX).unwrap_err();
125        let err: ReconstructionCacheError = int_err.into();
126        assert!(matches!(
127            err,
128            ReconstructionCacheError::NumericConversion(_)
129        ));
130    }
131
132    #[test]
133    fn debug_output_non_empty() {
134        let err = ReconstructionCacheError::EmptyRedisUrl;
135        let debug = format!("{err:?}");
136        assert!(!debug.is_empty());
137    }
138
139    #[test]
140    fn discriminant_matches_same_variant() {
141        let a = ReconstructionCacheError::EmptyRedisUrl;
142        let b = ReconstructionCacheError::EmptyRedisUrl;
143        assert_eq!(discriminant(&a), discriminant(&b));
144    }
145
146    #[test]
147    fn discriminant_differs_between_variants() {
148        let a = ReconstructionCacheError::EmptyRedisUrl;
149        let b = ReconstructionCacheError::Operation;
150        assert_ne!(discriminant(&a), discriminant(&b));
151    }
152}