axum_gate/accounts/
errors.rs

1//! Account-category native errors.
2//!
3//! This module provides category-native error types for the Accounts domain,
4//! used directly in handlers, services, and repositories.
5//!
6//! # Overview
7//! - `AccountsError`: domain-specific error enum for account operations
8//! - `AccountOperation`: operation discriminator used by `AccountsError`
9//!
10//! # Examples
11//! Basic construction and user-facing message extraction:
12//! ```rust
13//! use axum_gate::accounts::{AccountsError, AccountOperation};
14//! use axum_gate::errors::UserFriendlyError;
15//!
16//! let err = AccountsError::operation(
17//!     AccountOperation::Create,
18//!     "failed to persist account",
19//!     Some("user-123".into()),
20//! );
21//!
22//! assert!(err.user_message().contains("couldn't create your account"));
23//! assert!(err.developer_message().contains("Account operation create failed"));
24//! assert!(err.support_code().starts_with("ACCT-"));
25//! ```
26
27use crate::errors::{ErrorSeverity, UserFriendlyError};
28use std::collections::hash_map::DefaultHasher;
29use std::fmt;
30use std::hash::{Hash, Hasher};
31use thiserror::Error;
32
33/// Supported account operations for structured error reporting.
34#[derive(Debug, Clone)]
35pub enum AccountOperation {
36    /// Create account operation
37    Create,
38    /// Delete account operation
39    Delete,
40}
41
42impl fmt::Display for AccountOperation {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            AccountOperation::Create => write!(f, "create"),
46            AccountOperation::Delete => write!(f, "delete"),
47        }
48    }
49}
50
51/// Accounts domain errors (category-native).
52///
53/// These errors cover account-related operation failures.
54#[derive(Debug, Error)]
55#[non_exhaustive]
56pub enum AccountsError {
57    /// An account operation failed.
58    #[error("Account operation {operation} failed: {message}")]
59    Operation {
60        /// The account operation that failed.
61        operation: AccountOperation,
62        /// Description of the failure (non-sensitive).
63        message: String,
64        /// Optional related account identifier (sanitized).
65        account_id: Option<String>,
66    },
67}
68
69impl AccountsError {
70    /// Construct an operation failure.
71    pub fn operation(
72        operation: AccountOperation,
73        message: impl Into<String>,
74        account_id: Option<String>,
75    ) -> Self {
76        AccountsError::Operation {
77            operation,
78            message: message.into(),
79            account_id,
80        }
81    }
82
83    /// Deterministic, category-specific support code.
84    fn support_code_inner(&self) -> String {
85        let mut hasher = DefaultHasher::new();
86        match self {
87            AccountsError::Operation {
88                operation,
89                account_id,
90                ..
91            } => {
92                format!("ACCT-OP-{}-{:X}", operation.to_string().to_uppercase(), {
93                    format!("{:?}{:?}", operation, account_id).hash(&mut hasher);
94                    hasher.finish() % 10000
95                })
96            }
97        }
98    }
99}
100
101impl UserFriendlyError for AccountsError {
102    fn user_message(&self) -> String {
103        match self {
104            AccountsError::Operation { operation, .. } => match operation {
105                AccountOperation::Create => "We couldn't create your account right now. Please try again in a moment, or contact our support team if the problem continues.".to_string(),
106                AccountOperation::Delete => "We couldn't delete your account at this time. Please try again later, or contact our support team for assistance.".to_string(),
107            },
108        }
109    }
110
111    fn developer_message(&self) -> String {
112        match self {
113            AccountsError::Operation {
114                operation,
115                message,
116                account_id,
117            } => {
118                let account_context = account_id
119                    .as_ref()
120                    .map(|id| format!(" [Account: {}]", id))
121                    .unwrap_or_default();
122                format!(
123                    "Account operation {} failed: {}{}",
124                    operation, message, account_context
125                )
126            }
127        }
128    }
129
130    fn support_code(&self) -> String {
131        self.support_code_inner()
132    }
133
134    fn severity(&self) -> ErrorSeverity {
135        match self {
136            AccountsError::Operation { operation, .. } => match operation {
137                AccountOperation::Delete => ErrorSeverity::Critical,
138                _ => ErrorSeverity::Error,
139            },
140        }
141    }
142
143    fn suggested_actions(&self) -> Vec<String> {
144        match self {
145            AccountsError::Operation { operation, .. } => match operation {
146                AccountOperation::Create => vec![
147                    "Wait a moment and try creating your account again".to_string(),
148                    "Ensure all required fields are filled out correctly".to_string(),
149                    "Check your email for any verification requirements".to_string(),
150                    "Contact our support team if the problem continues".to_string(),
151                ],
152                AccountOperation::Delete => vec![
153                    "Contact our support team to assist with account deletion".to_string(),
154                    "Ensure you have completed any required pre-deletion steps".to_string(),
155                    "This operation may be temporarily unavailable for security reasons"
156                        .to_string(),
157                ],
158            },
159        }
160    }
161
162    fn is_retryable(&self) -> bool {
163        match self {
164            AccountsError::Operation { operation, .. } => {
165                !matches!(operation, AccountOperation::Delete)
166            }
167        }
168    }
169}