Rustelo/client/src/auth/errors.rs
2025-07-07 23:05:46 +01:00

164 lines
6.8 KiB
Rust

use crate::i18n::UseI18n;
use serde_json;
use shared::auth::AuthError;
/// Helper struct for handling authentication errors with internationalization
#[derive(Clone)]
pub struct AuthErrorHandler {
i18n: UseI18n,
}
impl AuthErrorHandler {
pub fn new(i18n: UseI18n) -> Self {
Self { i18n }
}
/// Convert a server response error to a localized error message
pub async fn handle_response_error(&self, response: &reqwasm::http::Response) -> String {
if let Ok(error_text) = response.text().await {
self.map_error_to_localized_message(&error_text)
} else {
self.i18n.t("unknown-error")
}
}
/// Map error text to localized message
pub fn map_error_to_localized_message(&self, error_text: &str) -> String {
let translation_key = self.map_error_to_translation_key(error_text);
self.i18n.t(&translation_key)
}
/// Map server errors to translation keys
pub fn map_error_to_translation_key(&self, error_text: &str) -> String {
// Try to parse as JSON first (standard API error response)
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(error_text) {
if let Some(message) = json_value.get("message").and_then(|m| m.as_str()) {
return self.map_error_message_to_key(message);
}
if let Some(errors) = json_value.get("errors").and_then(|e| e.as_array()) {
if let Some(first_error) = errors.first().and_then(|e| e.as_str()) {
return self.map_error_message_to_key(first_error);
}
}
}
// Fallback to direct message mapping
self.map_error_message_to_key(error_text)
}
/// Map error messages to translation keys
fn map_error_message_to_key(&self, message: &str) -> String {
let message_lower = message.to_lowercase();
match message_lower.as_str() {
msg if msg.contains("invalid credentials") => "invalid-credentials".to_string(),
msg if msg.contains("user not found") => "user-not-found".to_string(),
msg if msg.contains("email already exists") => "email-already-exists".to_string(),
msg if msg.contains("username already exists") => "username-already-exists".to_string(),
msg if msg.contains("invalid token") => "invalid-token".to_string(),
msg if msg.contains("token expired") => "token-expired".to_string(),
msg if msg.contains("insufficient permissions") => {
"insufficient-permissions".to_string()
}
msg if msg.contains("account not verified") => "account-not-verified".to_string(),
msg if msg.contains("account suspended") => "account-suspended".to_string(),
msg if msg.contains("rate limit exceeded") => "rate-limit-exceeded".to_string(),
msg if msg.contains("oauth") => "oauth-error".to_string(),
msg if msg.contains("database") => "database-error".to_string(),
msg if msg.contains("validation") => "validation-error".to_string(),
msg if msg.contains("login failed") => "login-failed".to_string(),
msg if msg.contains("registration failed") => "registration-failed".to_string(),
msg if msg.contains("session expired") => "session-expired".to_string(),
msg if msg.contains("profile") && msg.contains("failed") => {
"profile-update-failed".to_string()
}
msg if msg.contains("password") && msg.contains("failed") => {
"password-change-failed".to_string()
}
msg if msg.contains("network") => "network-error".to_string(),
msg if msg.contains("server") => "server-error".to_string(),
msg if msg.contains("internal") => "internal-error".to_string(),
_ => "unknown-error".to_string(),
}
}
/// Handle AuthError enum directly
pub fn handle_auth_error(&self, error: &AuthError) -> String {
let translation_key = match error {
AuthError::InvalidCredentials => "invalid-credentials",
AuthError::UserNotFound => "user-not-found",
AuthError::EmailAlreadyExists => "email-already-exists",
AuthError::UsernameAlreadyExists => "username-already-exists",
AuthError::InvalidToken => "invalid-token",
AuthError::TokenExpired => "token-expired",
AuthError::InsufficientPermissions => "insufficient-permissions",
AuthError::AccountNotVerified => "account-not-verified",
AuthError::AccountSuspended => "account-suspended",
AuthError::RateLimitExceeded => "rate-limit-exceeded",
AuthError::OAuthError(_) => "oauth-error",
AuthError::DatabaseError => "database-error",
AuthError::InternalError => "internal-error",
AuthError::ValidationError(_) => "validation-error",
};
self.i18n.t(translation_key)
}
/// Handle network errors
pub fn handle_network_error(&self) -> String {
self.i18n.t("network-error")
}
/// Handle generic request failures
pub fn handle_request_failure(&self, operation: &str) -> String {
match operation {
"login" => self.i18n.t("login-failed"),
"register" => self.i18n.t("registration-failed"),
"profile-update" => self.i18n.t("profile-update-failed"),
"password-change" => self.i18n.t("password-change-failed"),
_ => self.i18n.t("request-failed"),
}
}
/// Check if an error indicates session expiration
pub fn is_session_expired(&self, error_text: &str) -> bool {
let error_lower = error_text.to_lowercase();
error_lower.contains("session expired")
|| error_lower.contains("token expired")
|| error_lower.contains("invalid token")
|| error_lower.contains("unauthorized")
}
/// Get appropriate error message for session expiration
pub fn get_session_expired_message(&self) -> String {
self.i18n.t("session-expired")
}
}
/// Helper function to create an AuthErrorHandler
pub fn create_auth_error_handler(i18n: UseI18n) -> AuthErrorHandler {
AuthErrorHandler::new(i18n)
}
/// Trait for handling authentication errors consistently
pub trait AuthErrorHandling {
fn handle_auth_error(&self, error: &str) -> String;
fn handle_network_error(&self) -> String;
fn handle_session_expired(&self) -> String;
}
impl AuthErrorHandling for UseI18n {
fn handle_auth_error(&self, error: &str) -> String {
let handler = create_auth_error_handler(self.clone());
handler.map_error_to_localized_message(error)
}
fn handle_network_error(&self) -> String {
self.t("network-error")
}
fn handle_session_expired(&self) -> String {
self.t("session-expired")
}
}