Skip to main content

webgates_codecs/
errors.rs

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