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}