axum_gate/codecs/
errors.rs

1//! Codec-category native errors.
2//!
3//! This module defines category-native errors for codecs and JWT processing
4//! used directly in handlers, services, and middleware for codec/serialization
5//! and authentication token flows.
6//!
7//! # Overview
8//! - `CodecsError`: codec and serialization error enum
9//! - `JwtError`: JWT processing error enum
10//! - Operation enums: `CodecOperation`, `JwtOperation`
11//!
12//! # Examples
13//!
14//! Codec error:
15//! ```rust
16//! use axum_gate::codecs::{CodecsError, CodecOperation};
17//! use axum_gate::errors::UserFriendlyError;
18//!
19//! let err = CodecsError::codec(CodecOperation::Encode, "failed to encode payload");
20//! assert!(err.user_message().contains("process your data"));
21//! assert!(err.developer_message().contains("Codec contract violation"));
22//! assert!(err.support_code().starts_with("CODEC-"));
23//! ```
24//!
25
26//!
27//! JWT error:
28//! ```rust
29//! use axum_gate::codecs::{JwtError, JwtOperation};
30//! use axum_gate::errors::UserFriendlyError;
31//!
32//! let err = JwtError::processing(JwtOperation::Encode, "jwt encoding failed");
33//! assert!(err.support_code().starts_with("JWT-ENCODE"));
34//! assert!(err.is_retryable());
35//! ```
36
37use crate::errors::{ErrorSeverity, UserFriendlyError};
38use std::collections::hash_map::DefaultHasher;
39use std::fmt;
40use std::hash::{Hash, Hasher};
41use thiserror::Error;
42
43/// Codec operation types.
44#[derive(Debug, Clone)]
45pub enum CodecOperation {
46    /// Encode operation
47    Encode,
48    /// Decode operation
49    Decode,
50}
51
52impl fmt::Display for CodecOperation {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            CodecOperation::Encode => write!(f, "encode"),
56            CodecOperation::Decode => write!(f, "decode"),
57        }
58    }
59}
60
61/// JWT operation types.
62#[derive(Debug, Clone)]
63pub enum JwtOperation {
64    /// JWT encode operation
65    Encode,
66    /// JWT decode operation
67    Decode,
68    /// JWT validation operation
69    Validate,
70}
71
72impl fmt::Display for JwtOperation {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        match self {
75            JwtOperation::Encode => write!(f, "encode"),
76            JwtOperation::Decode => write!(f, "decode"),
77            JwtOperation::Validate => write!(f, "validate"),
78        }
79    }
80}
81
82/// Codec-category native error type (codecs + serialization).
83#[derive(Debug, Error)]
84#[non_exhaustive]
85pub enum CodecsError {
86    /// Codec contract violation (encode/decode/serialize/deserialize/validate).
87    #[error("Codec error: {operation} - {message}")]
88    Codec {
89        /// The codec operation that failed.
90        operation: CodecOperation,
91        /// Description of the error (non-sensitive).
92        message: String,
93        /// The payload type being processed.
94        payload_type: Option<String>,
95        /// Expected format or structure.
96        expected_format: Option<String>,
97    },
98}
99
100impl CodecsError {
101    /// Create a codec error.
102    pub fn codec(operation: CodecOperation, message: impl Into<String>) -> Self {
103        CodecsError::Codec {
104            operation,
105            message: message.into(),
106            payload_type: None,
107            expected_format: None,
108        }
109    }
110
111    /// Create a codec error with format context.
112    pub fn codec_with_format(
113        operation: CodecOperation,
114        message: impl Into<String>,
115        payload_type: Option<String>,
116        expected_format: Option<String>,
117    ) -> Self {
118        CodecsError::Codec {
119            operation,
120            message: message.into(),
121            payload_type,
122            expected_format,
123        }
124    }
125
126    fn support_code_inner(&self) -> String {
127        let mut hasher = DefaultHasher::new();
128        match self {
129            CodecsError::Codec {
130                operation,
131                payload_type,
132                ..
133            } => {
134                format!("CODEC-{}-{:X}", operation.to_string().to_uppercase(), {
135                    format!("{:?}{:?}", operation, payload_type).hash(&mut hasher);
136                    hasher.finish() % 10000
137                })
138            }
139        }
140    }
141}
142
143impl UserFriendlyError for CodecsError {
144    fn user_message(&self) -> String {
145        match self {
146            CodecsError::Codec { operation, .. } => match operation {
147                CodecOperation::Encode => {
148                    "We couldn't process your data in the required format. Please check your input and try again.".to_string()
149                }
150                CodecOperation::Decode => {
151                    "We received data in an unexpected format. This might be a temporary issue - please try again.".to_string()
152                }
153            },
154        }
155    }
156
157    fn developer_message(&self) -> String {
158        match self {
159            CodecsError::Codec {
160                operation,
161                message,
162                payload_type,
163                expected_format,
164            } => {
165                let payload_context = payload_type
166                    .as_ref()
167                    .map(|pt| format!(" [Payload: {}]", pt))
168                    .unwrap_or_default();
169                let format_context = expected_format
170                    .as_ref()
171                    .map(|ef| format!(" [Expected: {}]", ef))
172                    .unwrap_or_default();
173                format!(
174                    "Codec contract violation during {} operation: {}{}{}",
175                    operation, message, payload_context, format_context
176                )
177            }
178        }
179    }
180
181    fn support_code(&self) -> String {
182        self.support_code_inner()
183    }
184
185    fn severity(&self) -> ErrorSeverity {
186        match self {
187            CodecsError::Codec { operation, .. } => match operation {
188                CodecOperation::Encode | CodecOperation::Decode => ErrorSeverity::Error,
189            },
190        }
191    }
192
193    fn suggested_actions(&self) -> Vec<String> {
194        match self {
195            CodecsError::Codec { operation, .. } => match operation {
196                CodecOperation::Encode => vec![
197                    "Check that all required fields are filled out correctly".to_string(),
198                    "Ensure special characters are properly formatted".to_string(),
199                    "Try simplifying your input and gradually add complexity".to_string(),
200                    "Contact support if data formatting requirements are unclear".to_string(),
201                ],
202                CodecOperation::Decode => vec![
203                    "This is likely a temporary system issue".to_string(),
204                    "Try refreshing the page and repeating your action".to_string(),
205                    "Clear your browser cache if the problem persists".to_string(),
206                    "Contact support if you continue receiving malformed data".to_string(),
207                ],
208            },
209        }
210    }
211
212    fn is_retryable(&self) -> bool {
213        match self {
214            CodecsError::Codec { operation, .. } => match operation {
215                CodecOperation::Encode => true, // user can fix input
216                CodecOperation::Decode => true, // may be temporary
217            },
218        }
219    }
220}
221
222/// JWT-category native error type.
223#[derive(Debug, Error)]
224#[non_exhaustive]
225pub enum JwtError {
226    /// JWT processing failure.
227    #[error("JWT error: {operation} - {message}")]
228    Processing {
229        /// The JWT operation that failed.
230        operation: JwtOperation,
231        /// Description of the failure (non-sensitive).
232        message: String,
233        /// The token that caused the error (truncated for security).
234        token_preview: Option<String>,
235    },
236}
237
238impl JwtError {
239    /// Create a JWT processing error.
240    pub fn processing(operation: JwtOperation, message: impl Into<String>) -> Self {
241        JwtError::Processing {
242            operation,
243            message: message.into(),
244            token_preview: None,
245        }
246    }
247
248    /// Create a JWT processing error with token preview.
249    pub fn processing_with_preview(
250        operation: JwtOperation,
251        message: impl Into<String>,
252        token_preview: Option<String>,
253    ) -> Self {
254        JwtError::Processing {
255            operation,
256            message: message.into(),
257            token_preview,
258        }
259    }
260
261    fn support_code_inner(&self) -> String {
262        match self {
263            JwtError::Processing { operation, .. } => {
264                format!("JWT-{}", operation.to_string().to_uppercase())
265            }
266        }
267    }
268}
269
270impl UserFriendlyError for JwtError {
271    fn user_message(&self) -> String {
272        match self {
273            JwtError::Processing { operation, .. } => match operation {
274                JwtOperation::Encode => {
275                    "We're having trouble with the authentication system. Please try signing in again.".to_string()
276                }
277                JwtOperation::Decode | JwtOperation::Validate => {
278                    "Your session appears to be invalid. Please sign in again to continue.".to_string()
279                }
280            },
281        }
282    }
283
284    fn developer_message(&self) -> String {
285        match self {
286            JwtError::Processing {
287                operation,
288                message,
289                token_preview,
290            } => {
291                let token_context = token_preview
292                    .as_ref()
293                    .map(|t| format!(" [Token Preview: {}]", t))
294                    .unwrap_or_default();
295                format!(
296                    "JWT {} operation failed: {}{}",
297                    operation, message, token_context
298                )
299            }
300        }
301    }
302
303    fn support_code(&self) -> String {
304        self.support_code_inner()
305    }
306
307    fn severity(&self) -> ErrorSeverity {
308        match self {
309            JwtError::Processing { operation, .. } => match operation {
310                JwtOperation::Encode => ErrorSeverity::Error,
311                _ => ErrorSeverity::Warning,
312            },
313        }
314    }
315
316    fn suggested_actions(&self) -> Vec<String> {
317        match self {
318            JwtError::Processing { operation, .. } => match operation {
319                JwtOperation::Encode => vec![
320                    "Try signing in again".to_string(),
321                    "Clear your browser cookies and try again".to_string(),
322                    "Contact support if you cannot sign in after multiple attempts".to_string(),
323                ],
324                JwtOperation::Decode | JwtOperation::Validate => vec![
325                    "Sign out completely and sign back in".to_string(),
326                    "Clear your browser cache and cookies".to_string(),
327                    "Try using a different browser or incognito mode".to_string(),
328                ],
329            },
330        }
331    }
332
333    fn is_retryable(&self) -> bool {
334        match self {
335            JwtError::Processing { operation, .. } => match operation {
336                JwtOperation::Encode => true, // user can retry auth
337                _ => true,
338            },
339        }
340    }
341}