Skip to main content

reallyme_valkey_kit/
error.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use thiserror::Error;
6
7/// Valkey configuration field identifier.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ValkeyConfigField {
10    /// Prefix used to derive Valkey environment-variable names.
11    EnvironmentPrefix,
12    /// Server hostname or IP address.
13    Host,
14    /// Server TCP port.
15    Port,
16    /// Logical database number.
17    Database,
18    /// Key namespace prefix.
19    KeyPrefix,
20    /// Optional ACL username.
21    Username,
22    /// Optional password or access token.
23    Password,
24    /// Connection establishment deadline.
25    ConnectionTimeout,
26    /// Command response deadline.
27    ResponseTimeout,
28    /// Automatic reconnect attempt count.
29    RetryAttempts,
30    /// Concurrent in-flight command ceiling.
31    ConcurrencyLimit,
32    /// Outbound command queue bound.
33    PipelineBufferSize,
34    /// Transport security mode.
35    TransportSecurity,
36    /// Private TLS CA certificate path.
37    TlsCaCertificatePath,
38}
39
40/// Stable Valkey configuration failure reason.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ValkeyConfigErrorReason {
43    /// A required field was empty.
44    Empty,
45    /// A field exceeded its fixed bound.
46    TooLarge,
47    /// A field did not match its required syntax.
48    InvalidSyntax,
49    /// A numeric field was zero.
50    MustBePositive,
51    /// An environment value was not valid Unicode.
52    InvalidEncoding,
53    /// A value conflicts with another explicitly selected option.
54    Incompatible,
55}
56
57/// Stable Valkey connection setup failure reason.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum ValkeySetupErrorReason {
60    /// The configured endpoint could not be represented by the client.
61    InvalidEndpoint,
62    /// TLS provider initialization was unavailable.
63    TlsProviderUnavailable,
64    /// The initial connection or authentication handshake failed.
65    ConnectionUnavailable,
66    /// The server rejected configured authentication credentials.
67    AuthenticationRejected,
68    /// A custom TLS trust file could not be loaded.
69    TlsTrustUnavailable,
70    /// A custom TLS trust file was malformed or contained no certificates.
71    TlsTrustInvalid,
72    /// A custom TLS trust file exceeded the bounded startup policy.
73    TlsTrustTooLarge,
74}
75
76/// Stable Valkey command failure reason.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum ValkeyCommandErrorReason {
79    /// The connection was unavailable.
80    ConnectionUnavailable,
81    /// A command exceeded its response deadline.
82    Timeout,
83    /// The server rejected a command or its arguments.
84    Rejected,
85    /// The response could not be decoded into the required type.
86    InvalidResponse,
87}
88
89/// Conservative retry guidance for an app-owned Valkey operation.
90///
91/// The hint does not prove that an operation is idempotent. Applications must
92/// still enforce a bounded deadline and decide whether replaying their complete
93/// operation is safe.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ValkeyRetryHint {
96    /// Do not retry automatically.
97    DoNotRetry,
98    /// Retry only when the complete operation is idempotent.
99    RetryIdempotentOperation,
100    /// Retry an idempotent operation after bounded randomized backoff.
101    RetryIdempotentOperationAfterBackoff,
102}
103
104impl ValkeyCommandErrorReason {
105    /// Returns conservative retry guidance for this failure category.
106    pub const fn retry_hint(self) -> ValkeyRetryHint {
107        match self {
108            Self::ConnectionUnavailable => ValkeyRetryHint::RetryIdempotentOperationAfterBackoff,
109            Self::Timeout => ValkeyRetryHint::RetryIdempotentOperation,
110            Self::Rejected | Self::InvalidResponse => ValkeyRetryHint::DoNotRetry,
111        }
112    }
113}
114
115/// Bounded client-side data kind.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum ValkeyDataKind {
118    /// Binary key suffix.
119    Key,
120    /// Binary value.
121    Value,
122    /// Expiration interval.
123    TimeToLive,
124}
125
126/// Stable client-side data validation reason.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum ValkeyDataErrorReason {
129    /// A required value was empty.
130    Empty,
131    /// A bounded value exceeded its maximum size.
132    TooLarge,
133    /// A numeric value was outside its accepted range.
134    OutOfRange,
135}
136
137/// Typed Valkey kit error without server text, keys, values, or credentials.
138#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
139pub enum ValkeyError {
140    /// Static configuration validation failed.
141    #[error("valkey configuration is invalid")]
142    Config {
143        /// Field that failed validation.
144        field: ValkeyConfigField,
145        /// Stable validation reason.
146        reason: ValkeyConfigErrorReason,
147    },
148    /// Initial client or connection setup failed.
149    #[error("valkey client setup failed")]
150    Setup {
151        /// Stable setup reason.
152        reason: ValkeySetupErrorReason,
153    },
154    /// A command failed.
155    #[error("valkey command failed")]
156    Command {
157        /// Stable command failure reason.
158        reason: ValkeyCommandErrorReason,
159    },
160    /// A key, value, or TTL failed local validation.
161    #[error("valkey operation data is invalid")]
162    InvalidData {
163        /// Kind of value that failed validation.
164        kind: ValkeyDataKind,
165        /// Stable validation reason.
166        reason: ValkeyDataErrorReason,
167    },
168}
169
170/// Result alias for Valkey kit operations.
171pub type ValkeyResult<T> = Result<T, ValkeyError>;
172
173#[cfg(test)]
174mod tests {
175    use super::{ValkeyCommandErrorReason, ValkeyRetryHint};
176
177    #[test]
178    fn retry_hints_never_claim_an_operation_is_safe_to_replay() {
179        assert_eq!(
180            ValkeyCommandErrorReason::ConnectionUnavailable.retry_hint(),
181            ValkeyRetryHint::RetryIdempotentOperationAfterBackoff
182        );
183        assert_eq!(
184            ValkeyCommandErrorReason::Timeout.retry_hint(),
185            ValkeyRetryHint::RetryIdempotentOperation
186        );
187        assert_eq!(
188            ValkeyCommandErrorReason::Rejected.retry_hint(),
189            ValkeyRetryHint::DoNotRetry
190        );
191        assert_eq!(
192            ValkeyCommandErrorReason::InvalidResponse.retry_hint(),
193            ValkeyRetryHint::DoNotRetry
194        );
195    }
196}