1use 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#[derive(Debug, Clone)]
15pub enum CodecOperation {
16 Encode,
18 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#[derive(Debug, Clone)]
33pub enum JwtOperation {
34 Encode,
36 Decode,
38 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#[derive(Debug, Error)]
54#[non_exhaustive]
55pub enum CodecsError {
56 #[error("Codec error: {operation} - {message}")]
58 Codec {
59 operation: CodecOperation,
61 message: String,
63 payload_type: Option<String>,
65 expected_format: Option<String>,
67 },
68}
69
70impl CodecsError {
71 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 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#[derive(Debug, Error)]
185#[non_exhaustive]
186pub enum JwtError {
187 #[error("JWT error: {operation} - {message}")]
189 Processing {
190 operation: JwtOperation,
192 message: String,
194 token_preview: Option<String>,
196 },
197}
198
199impl JwtError {
200 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 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}