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}