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