1use crate::errors::{ErrorSeverity, UserFriendlyError};
48use std::collections::hash_map::DefaultHasher;
49use std::fmt;
50use std::hash::{Hash, Hasher};
51use thiserror::Error;
52
53#[derive(Debug, Clone)]
55pub enum RepositoryType {
56 Account,
58 Secret,
60 PermissionMapping,
62}
63
64impl fmt::Display for RepositoryType {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 RepositoryType::Account => write!(f, "account"),
68 RepositoryType::Secret => write!(f, "secret"),
69 RepositoryType::PermissionMapping => write!(f, "permission_mapping"),
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub enum RepositoryOperation {
77 Insert,
79 Get,
81 Update,
83 Delete,
85}
86
87impl fmt::Display for RepositoryOperation {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 match self {
90 RepositoryOperation::Insert => write!(f, "insert"),
91 RepositoryOperation::Get => write!(f, "get"),
92 RepositoryOperation::Update => write!(f, "update"),
93 RepositoryOperation::Delete => write!(f, "delete"),
94 }
95 }
96}
97
98#[derive(Debug, Error)]
100#[non_exhaustive]
101pub enum RepositoriesError {
102 #[error("Repository error: {repository} {operation} - {message}")]
104 OperationFailed {
105 repository: RepositoryType,
107 operation: RepositoryOperation,
109 message: String,
111 key: Option<String>,
113 context: Option<String>,
115 },
116
117 #[error("Repository not found: {repository} - {key:?}")]
119 NotFound {
120 repository: RepositoryType,
122 key: Option<String>,
124 },
125
126 #[error("Repository constraint: {repository} - {message}")]
128 Constraint {
129 repository: RepositoryType,
131 message: String,
133 key: Option<String>,
135 },
136}
137
138impl RepositoriesError {
139 pub fn operation_failed(
141 repository: RepositoryType,
142 operation: RepositoryOperation,
143 message: impl Into<String>,
144 key: Option<String>,
145 context: Option<String>,
146 ) -> Self {
147 RepositoriesError::OperationFailed {
148 repository,
149 operation,
150 message: message.into(),
151 key,
152 context,
153 }
154 }
155
156 pub fn not_found(repository: RepositoryType, key: Option<String>) -> Self {
158 RepositoriesError::NotFound { repository, key }
159 }
160
161 pub fn constraint(
163 repository: RepositoryType,
164 message: impl Into<String>,
165 key: Option<String>,
166 ) -> Self {
167 RepositoriesError::Constraint {
168 repository,
169 message: message.into(),
170 key,
171 }
172 }
173
174 fn support_code_inner(&self) -> String {
175 let mut hasher = DefaultHasher::new();
176 match self {
177 RepositoriesError::OperationFailed {
178 repository,
179 operation,
180 key,
181 ..
182 } => {
183 format!(
184 "REPO-{}-{}-{:X}",
185 repository.to_string().to_uppercase(),
186 operation.to_string().to_uppercase(),
187 {
188 format!("{:?}{:?}", repository, key).hash(&mut hasher);
189 hasher.finish() % 10000
190 }
191 )
192 }
193 RepositoriesError::NotFound { repository, key } => {
194 format!(
195 "REPO-{}-NOTFOUND-{:X}",
196 repository.to_string().to_uppercase(),
197 {
198 format!("{:?}{:?}", repository, key).hash(&mut hasher);
199 hasher.finish() % 10000
200 }
201 )
202 }
203 RepositoriesError::Constraint {
204 repository, key, ..
205 } => {
206 format!(
207 "REPO-{}-CONSTRAINT-{:X}",
208 repository.to_string().to_uppercase(),
209 {
210 format!("{:?}{:?}", repository, key).hash(&mut hasher);
211 hasher.finish() % 10000
212 }
213 )
214 }
215 }
216 }
217}
218
219impl UserFriendlyError for RepositoriesError {
220 fn user_message(&self) -> String {
221 match self {
222 RepositoriesError::OperationFailed { repository, .. } => match repository {
223 RepositoryType::Account => "We're having trouble accessing your account information. Please try refreshing the page or signing in again.".to_string(),
224 RepositoryType::Secret => "There's an issue with the security system. Please try again or contact support if the problem continues.".to_string(),
225 RepositoryType::PermissionMapping => "We're having trouble with the permission system. Your permissions are still active, but some features might not display correctly.".to_string(),
226 },
227 RepositoriesError::NotFound { repository, .. } => match repository {
228 RepositoryType::Account => "We couldn't find an account with the requested identifier.".to_string(),
229 RepositoryType::Secret => "We couldn't find the requested security information.".to_string(),
230 RepositoryType::PermissionMapping => "We couldn't find the requested permission mapping.".to_string(),
231 },
232 RepositoriesError::Constraint { repository, .. } => match repository {
233 RepositoryType::Account => "We couldn't complete this request due to an account constraint. Please review your input and try again.".to_string(),
234 RepositoryType::Secret => "We couldn't complete this request due to a security constraint. Please try again later.".to_string(),
235 RepositoryType::PermissionMapping => "We couldn't update the permission mapping due to a constraint. Your permissions remain unchanged.".to_string(),
236 },
237 }
238 }
239
240 fn developer_message(&self) -> String {
241 match self {
242 RepositoriesError::OperationFailed {
243 repository,
244 operation,
245 message,
246 key,
247 context,
248 } => {
249 let key_s = key
250 .as_ref()
251 .map(|k| format!(" [Key: {}]", k))
252 .unwrap_or_default();
253 let ctx_s = context
254 .as_ref()
255 .map(|c| format!(" [Context: {}]", c))
256 .unwrap_or_default();
257 format!(
258 "Repository operation failed in {} repository ({}): {}{}{}",
259 repository, operation, message, key_s, ctx_s
260 )
261 }
262 RepositoriesError::NotFound { repository, key } => {
263 let key_s = key
264 .as_ref()
265 .map(|k| format!(" [Key: {}]", k))
266 .unwrap_or_default();
267 format!(
268 "Repository entity not found in {} repository.{}",
269 repository, key_s
270 )
271 }
272 RepositoriesError::Constraint {
273 repository,
274 message,
275 key,
276 } => {
277 let key_s = key
278 .as_ref()
279 .map(|k| format!(" [Key: {}]", k))
280 .unwrap_or_default();
281 format!(
282 "Repository constraint violation in {} repository: {}{}",
283 repository, message, key_s
284 )
285 }
286 }
287 }
288
289 fn support_code(&self) -> String {
290 self.support_code_inner()
291 }
292
293 fn severity(&self) -> ErrorSeverity {
294 match self {
295 RepositoriesError::OperationFailed {
296 repository,
297 operation,
298 ..
299 } => match (repository, operation) {
300 (RepositoryType::Secret, _) => ErrorSeverity::Critical,
301 (RepositoryType::Account, RepositoryOperation::Delete) => ErrorSeverity::Critical,
302 _ => ErrorSeverity::Error,
303 },
304 RepositoriesError::NotFound { repository, .. } => match repository {
305 RepositoryType::Account => ErrorSeverity::Warning,
306 _ => ErrorSeverity::Info,
307 },
308 RepositoriesError::Constraint { repository, .. } => match repository {
309 RepositoryType::Account | RepositoryType::Secret => ErrorSeverity::Error,
310 _ => ErrorSeverity::Warning,
311 },
312 }
313 }
314
315 fn suggested_actions(&self) -> Vec<String> {
316 match self {
317 RepositoriesError::OperationFailed {
318 repository,
319 operation,
320 ..
321 } => match (repository, operation) {
322 (RepositoryType::Account, RepositoryOperation::Insert) => vec![
323 "Ensure the account identifier is unique".to_string(),
324 "Verify required fields are provided".to_string(),
325 "Try your request again".to_string(),
326 "Contact support if the problem persists".to_string(),
327 ],
328 (RepositoryType::Secret, _) => vec![
329 "Do not retry password or secret operations repeatedly".to_string(),
330 "Contact support if security operations continue to fail".to_string(),
331 ],
332 _ => vec![
333 "Try your request again in a moment".to_string(),
334 "Refresh the page and attempt the operation again".to_string(),
335 "Contact support if issues persist".to_string(),
336 ],
337 },
338 RepositoriesError::NotFound { repository, .. } => match repository {
339 RepositoryType::Account => vec![
340 "Verify the account identifier is correct".to_string(),
341 "Ensure you are signed in with the correct account".to_string(),
342 "Contact support if the account should exist".to_string(),
343 ],
344 _ => vec![
345 "Verify the requested identifier is correct".to_string(),
346 "Refresh the page and try again".to_string(),
347 ],
348 },
349 RepositoriesError::Constraint { repository, .. } => match repository {
350 RepositoryType::Account => vec![
351 "Review the input for conflicting or duplicate values".to_string(),
352 "Ensure unique identifiers are not reused".to_string(),
353 "Try again after correcting the input".to_string(),
354 ],
355 _ => vec![
356 "Review your input for constraint issues".to_string(),
357 "Try your request again in a moment".to_string(),
358 "Contact support if constraints are unclear".to_string(),
359 ],
360 },
361 }
362 }
363
364 fn is_retryable(&self) -> bool {
365 match self {
366 RepositoriesError::OperationFailed {
367 repository,
368 operation,
369 ..
370 } => {
371 match (repository, operation) {
372 (RepositoryType::Secret, _) => false, _ => true,
374 }
375 }
376 RepositoriesError::NotFound { .. } => false,
377 RepositoriesError::Constraint { .. } => false,
378 }
379 }
380}
381
382#[derive(Debug, Clone)]
384pub enum DatabaseOperation {
385 Connect,
387 Query,
389 Insert,
391 Update,
393 Delete,
395 Migration,
397 Backup,
399 Transaction,
401}
402
403impl fmt::Display for DatabaseOperation {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 match self {
406 DatabaseOperation::Connect => write!(f, "connect"),
407 DatabaseOperation::Query => write!(f, "query"),
408 DatabaseOperation::Insert => write!(f, "insert"),
409 DatabaseOperation::Update => write!(f, "update"),
410 DatabaseOperation::Delete => write!(f, "delete"),
411 DatabaseOperation::Migration => write!(f, "migration"),
412 DatabaseOperation::Backup => write!(f, "backup"),
413 DatabaseOperation::Transaction => write!(f, "transaction"),
414 }
415 }
416}
417
418#[derive(Debug, Error)]
420#[non_exhaustive]
421pub enum DatabaseError {
422 #[error("Database error: {operation} - {message}")]
424 Operation {
425 operation: DatabaseOperation,
427 message: String,
429 table: Option<String>,
431 record_id: Option<String>,
433 },
434}
435
436impl DatabaseError {
437 pub fn new(operation: DatabaseOperation, message: impl Into<String>) -> Self {
439 DatabaseError::Operation {
440 operation,
441 message: message.into(),
442 table: None,
443 record_id: None,
444 }
445 }
446
447 pub fn with_context(
449 operation: DatabaseOperation,
450 message: impl Into<String>,
451 table: Option<String>,
452 record_id: Option<String>,
453 ) -> Self {
454 DatabaseError::Operation {
455 operation,
456 message: message.into(),
457 table,
458 record_id,
459 }
460 }
461
462 fn support_code_inner(&self) -> String {
463 let mut hasher = DefaultHasher::new();
464 match self {
465 DatabaseError::Operation {
466 operation, table, ..
467 } => {
468 format!("DB-{}-{:X}", operation.to_string().to_uppercase(), {
469 format!("{:?}{:?}", operation, table).hash(&mut hasher);
470 hasher.finish() % 10000
471 })
472 }
473 }
474 }
475}
476
477impl UserFriendlyError for DatabaseError {
478 fn user_message(&self) -> String {
479 match self {
480 DatabaseError::Operation { operation, .. } => match operation {
481 DatabaseOperation::Connect => {
482 "We're having trouble connecting to our database. Please try again in a moment."
483 .to_string()
484 }
485 DatabaseOperation::Query
486 | DatabaseOperation::Insert
487 | DatabaseOperation::Update
488 | DatabaseOperation::Delete => {
489 "We're experiencing technical difficulties with our data services. Please try again shortly.".to_string()
490 }
491 DatabaseOperation::Migration | DatabaseOperation::Backup => {
492 "Our system is currently undergoing maintenance. Please try again later."
493 .to_string()
494 }
495 DatabaseOperation::Transaction => {
496 "We couldn't complete your request due to a technical issue. Please try again."
497 .to_string()
498 }
499 },
500 }
501 }
502
503 fn developer_message(&self) -> String {
504 match self {
505 DatabaseError::Operation {
506 operation,
507 message,
508 table,
509 record_id,
510 } => {
511 let table_context = table
512 .as_ref()
513 .map(|t| format!(" [Table: {}]", t))
514 .unwrap_or_default();
515 let record_context = record_id
516 .as_ref()
517 .map(|r| format!(" [Record: {}]", r))
518 .unwrap_or_default();
519 format!(
520 "Database {} operation failed: {}{}{}",
521 operation, message, table_context, record_context
522 )
523 }
524 }
525 }
526
527 fn support_code(&self) -> String {
528 self.support_code_inner()
529 }
530
531 fn severity(&self) -> ErrorSeverity {
532 match self {
533 DatabaseError::Operation { operation, .. } => match operation {
534 DatabaseOperation::Connect => ErrorSeverity::Critical,
535 DatabaseOperation::Migration | DatabaseOperation::Backup => ErrorSeverity::Critical,
536 _ => ErrorSeverity::Error,
537 },
538 }
539 }
540
541 fn suggested_actions(&self) -> Vec<String> {
542 match self {
543 DatabaseError::Operation { operation, .. } => match operation {
544 DatabaseOperation::Connect => vec![
545 "Wait a few minutes and try again".to_string(),
546 "Check our status page for any database maintenance notifications".to_string(),
547 "Contact support if the issue persists for more than 15 minutes".to_string(),
548 ],
549 DatabaseOperation::Query
550 | DatabaseOperation::Insert
551 | DatabaseOperation::Update
552 | DatabaseOperation::Delete => vec![
553 "Try your request again in a moment".to_string(),
554 "Refresh the page and attempt the operation again".to_string(),
555 "Save your work locally if possible and try again later".to_string(),
556 "Contact support if you continue to experience issues".to_string(),
557 ],
558 DatabaseOperation::Migration | DatabaseOperation::Backup => vec![
559 "This is a system maintenance issue that will be resolved automatically"
560 .to_string(),
561 "Check our status page for maintenance schedules".to_string(),
562 "No action is required from you at this time".to_string(),
563 ],
564 DatabaseOperation::Transaction => vec![
565 "Try completing your transaction again".to_string(),
566 "Ensure all required information is provided".to_string(),
567 "Contact support if the transaction continues to fail".to_string(),
568 ],
569 },
570 }
571 }
572
573 fn is_retryable(&self) -> bool {
574 match self {
575 DatabaseError::Operation { operation, .. } => match operation {
576 DatabaseOperation::Connect => true, DatabaseOperation::Migration | DatabaseOperation::Backup => false, _ => true,
579 },
580 }
581 }
582}