axum_gate/hashing/errors.rs
1//! Hashing-category native errors.
2//!
3//! This module defines category-native error types for hashing and verification,
4//! used directly in handlers, services, and middleware for hashing,
5//! password verification, and secret management flows.
6//!
7//! # Overview
8//!
9//! - `HashingError`: category-native error enum for hashing/verification
10//! - `HashingOperation`: operation discriminator used by `HashingError`
11//!
12//! # Examples
13//!
14//! Construct a hashing error with algorithm context:
15//! ```rust
16//! use axum_gate::hashing::{HashingError, HashingOperation};
17//!
18//! let err = HashingError::with_algorithm(
19//! HashingOperation::Hash,
20//! "argon2 hashing failed",
21//! "argon2id",
22//! );
23//!
24//! // Utility methods from the unified trait
25//! use axum_gate::errors::UserFriendlyError;
26//! assert!(err.user_message().contains("security processing system"));
27//! assert!(err.developer_message().contains("Hash operation hash failed"));
28//! assert!(err.support_code().starts_with("HASH-HASH"));
29//! ```
30//!
31//! Construct a verification failure where retry is possible:
32//! ```rust
33//! use axum_gate::hashing::{HashingError, HashingOperation};
34//! use axum_gate::errors::UserFriendlyError;
35//!
36//! let err = HashingError::with_context(
37//! HashingOperation::Verify,
38//! "password verification failed",
39//! Some("argon2id".into()),
40//! Some("$argon2id$v=19$...".into()),
41//! );
42//! assert!(err.is_retryable());
43//! ```
44
45use crate::errors::{ErrorSeverity, UserFriendlyError};
46use std::collections::hash_map::DefaultHasher;
47use std::fmt;
48use std::hash::{Hash, Hasher};
49use thiserror::Error;
50
51/// Hashing operation identifiers used for structured error reporting.
52#[derive(Debug, Clone)]
53pub enum HashingOperation {
54 /// Compute a new hash for a provided plaintext value.
55 Hash,
56 /// Verify a plaintext value against an existing hash.
57 Verify,
58}
59
60impl fmt::Display for HashingOperation {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 HashingOperation::Hash => write!(f, "hash"),
64 HashingOperation::Verify => write!(f, "verify"),
65 }
66 }
67}
68
69/// Hashing-category native errors (hashing and verification).
70///
71/// Use these errors in hashing services, verification flows, and password/secret updates.
72#[derive(Debug, Error)]
73#[non_exhaustive]
74pub enum HashingError {
75 /// A hashing-related operation failed.
76 #[error("Hashing error: {operation} - {message}")]
77 Operation {
78 /// The hashing operation that failed.
79 operation: HashingOperation,
80 /// Description of the error (non-sensitive).
81 message: String,
82 /// The hashing algorithm used (e.g., `argon2id`, `bcrypt`) if known.
83 algorithm: Option<String>,
84 /// Expected hash format (sanitized) if relevant.
85 expected_format: Option<String>,
86 },
87}
88
89impl HashingError {
90 /// Construct a hashing error without algorithm/format context.
91 ///
92 /// # Arguments
93 /// - `operation`: The hashing operation that failed.
94 /// - `message`: A non-sensitive description of the error.
95 ///
96 /// # Examples
97 /// ```rust
98 /// use axum_gate::hashing::{HashingError, HashingOperation};
99 /// let _err = HashingError::new(HashingOperation::Hash, "failed to hash value");
100 /// ```
101 pub fn new(operation: HashingOperation, message: impl Into<String>) -> Self {
102 HashingError::Operation {
103 operation,
104 message: message.into(),
105 algorithm: None,
106 expected_format: None,
107 }
108 }
109
110 /// Construct a hashing error with algorithm context.
111 ///
112 /// # Arguments
113 /// - `operation`: The hashing operation that failed.
114 /// - `message`: A non-sensitive description of the error.
115 /// - `algorithm`: The hashing algorithm (e.g., `argon2id`).
116 ///
117 /// # Examples
118 /// ```rust
119 /// use axum_gate::hashing::{HashingError, HashingOperation};
120 /// let _err = HashingError::with_algorithm(HashingOperation::Verify, "verification failed", "argon2id");
121 /// ```
122 pub fn with_algorithm(
123 operation: HashingOperation,
124 message: impl Into<String>,
125 algorithm: impl Into<String>,
126 ) -> Self {
127 HashingError::Operation {
128 operation,
129 message: message.into(),
130 algorithm: Some(algorithm.into()),
131 expected_format: None,
132 }
133 }
134
135 /// Construct a hashing error with full context.
136 ///
137 /// # Arguments
138 /// - `operation`: The hashing operation that failed.
139 /// - `message`: A non-sensitive description of the error.
140 /// - `algorithm`: The hashing algorithm (e.g., `argon2id`), if known.
141 /// - `expected_format`: Expected hash format (sanitized), if relevant.
142 ///
143 /// # Examples
144 /// ```rust
145 /// use axum_gate::hashing::{HashingError, HashingOperation};
146 /// let _err = HashingError::with_context(
147 /// HashingOperation::Verify,
148 /// "verification failed",
149 /// Some("argon2id".into()),
150 /// Some("$argon2id$v=19$...".into()),
151 /// );
152 /// ```
153 pub fn with_context(
154 operation: HashingOperation,
155 message: impl Into<String>,
156 algorithm: Option<String>,
157 expected_format: Option<String>,
158 ) -> Self {
159 HashingError::Operation {
160 operation,
161 message: message.into(),
162 algorithm,
163 expected_format,
164 }
165 }
166
167 /// Deterministic, category-specific support code for this error.
168 fn support_code_inner(&self) -> String {
169 let mut hasher = DefaultHasher::new();
170 match self {
171 HashingError::Operation {
172 operation,
173 algorithm,
174 ..
175 } => {
176 format!("HASH-{}-{:X}", operation.to_string().to_uppercase(), {
177 format!("{:?}{:?}", operation, algorithm).hash(&mut hasher);
178 hasher.finish() % 10000
179 })
180 }
181 }
182 }
183}
184
185impl UserFriendlyError for HashingError {
186 fn user_message(&self) -> String {
187 match self {
188 HashingError::Operation { operation, .. } => match operation {
189 HashingOperation::Hash => {
190 "There's an issue with the security processing system. Please try again in a moment."
191 .to_string()
192 }
193 HashingOperation::Verify => {
194 "We couldn't verify your credentials due to a technical issue. Please try signing in again."
195 .to_string()
196 }
197 },
198 }
199 }
200
201 fn developer_message(&self) -> String {
202 match self {
203 HashingError::Operation {
204 operation,
205 message,
206 algorithm,
207 expected_format,
208 } => {
209 let algorithm_context = algorithm
210 .as_ref()
211 .map(|a| format!(" [Algorithm: {}]", a))
212 .unwrap_or_default();
213 let format_context = expected_format
214 .as_ref()
215 .map(|ef| format!(" [Expected: {}]", ef))
216 .unwrap_or_default();
217 format!(
218 "Hash operation {} failed: {}{}{}",
219 operation, message, algorithm_context, format_context
220 )
221 }
222 }
223 }
224
225 fn support_code(&self) -> String {
226 self.support_code_inner()
227 }
228
229 fn severity(&self) -> ErrorSeverity {
230 match self {
231 HashingError::Operation { operation, .. } => match operation {
232 HashingOperation::Hash => ErrorSeverity::Critical,
233 HashingOperation::Verify => ErrorSeverity::Critical,
234 },
235 }
236 }
237
238 fn suggested_actions(&self) -> Vec<String> {
239 match self {
240 HashingError::Operation { operation, .. } => match operation {
241 HashingOperation::Hash => vec![
242 "This is a critical security system error".to_string(),
243 "Contact our support team immediately".to_string(),
244 "Do not retry operations that involve password or secret changes".to_string(),
245 "Use secure communication when reporting this issue".to_string(),
246 ],
247 HashingOperation::Verify => vec![
248 "Double-check your password for typos".to_string(),
249 "Ensure Caps Lock is not accidentally enabled".to_string(),
250 "If you're certain your password is correct, contact support".to_string(),
251 "Try using password recovery if verification continues to fail".to_string(),
252 ],
253 },
254 }
255 }
256
257 fn is_retryable(&self) -> bool {
258 match self {
259 HashingError::Operation { operation, .. } => match operation {
260 HashingOperation::Hash => false, // critical system condition
261 HashingOperation::Verify => true, // user can retry with correct credentials
262 },
263 }
264 }
265}