Files
bakit/src/route/contact/errors.rs

168 lines
7.2 KiB
Rust
Raw Normal View History

use std::error::Error;
use lettre::address::AddressError;
use poem_openapi::payload::Json;
use validator::ValidationErrors;
use super::ContactResponse;
/// Errors that can occur during contact form processing and email sending.
#[derive(Debug)]
pub enum ContactError {
/// The email address provided in the contact form request could not be parsed.
///
/// This typically indicates the user submitted an invalid email address format.
CouldNotParseRequestEmailAddress(String),
/// The email address configured in application settings could not be parsed.
///
/// This indicates a configuration error with the sender or recipient email addresses.
CouldNotParseSettingsEmail(String),
/// Failed to construct the email message.
///
/// This can occur due to invalid message content or headers.
FailedToBuildMessage(String),
/// Failed to send the email through the SMTP server.
///
/// This can occur due to network issues, authentication failures, or SMTP server errors.
CouldNotSendEmail(String),
/// A general validation error occurred that doesn't fit specific field validation.
///
/// This is used for validation errors that don't map to a specific form field.
ValidationError(String),
/// The name field in the contact form failed validation.
///
/// This typically occurs when the name is empty, too short, or contains invalid characters.
ValidationNameError(String),
/// The email field in the contact form failed validation.
///
/// This typically occurs when the email address format is invalid.
ValidationEmailError(String),
/// The message field in the contact form failed validation.
///
/// This typically occurs when the message is empty, too short.
ValidationMessageError(String),
/// An unspecified internal error occurred.
OtherError(String),
}
impl Error for ContactError {}
/// Converts a lettre SMTP transport error into a `ContactError`.
///
/// SMTP errors are logged at ERROR level with full details, then
/// mapped to `OtherError` as they represent server-side or network
/// issues beyond the client's control.
impl From<lettre::transport::smtp::Error> for ContactError {
fn from(value: lettre::transport::smtp::Error) -> Self {
tracing::event!(target: "contact", tracing::Level::ERROR, "SMTP Error details: {}", format!("{value:?}"));
Self::OtherError(value.to_string())
}
}
impl std::fmt::Display for ContactError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = match self {
Self::CouldNotParseRequestEmailAddress(e) => {
format!("Failed to parse requester's email address: {e:?}")
}
Self::CouldNotParseSettingsEmail(e) => {
format!("Failed to parse email address in settings: {e:?}")
}
Self::FailedToBuildMessage(e) => {
format!("Failed to build the message to be sent: {e:?}")
}
Self::CouldNotSendEmail(e) => format!("Failed to send the email: {e:?}"),
Self::ValidationError(e) => format!("Failed to validate request: {e:?}"),
Self::ValidationNameError(e) => format!("Failed to validate name: {e:?}"),
Self::ValidationEmailError(e) => format!("Failed to validate email: {e:?}"),
Self::ValidationMessageError(e) => format!("Failed to validate message: {e:?}"),
Self::OtherError(e) => format!("Other internal error: {e:?}"),
};
write!(f, "{message}")
}
}
/// Converts validation errors into a `ContactError`.
///
/// This implementation inspects the validation errors to determine which specific field
/// failed validation (name, email, or message) and returns the appropriate variant.
/// If no specific field can be identified, returns a generic `ValidationError`.
impl From<ValidationErrors> for ContactError {
fn from(value: ValidationErrors) -> Self {
if validator::ValidationErrors::has_error(&Err(value.clone()), "name") {
return Self::ValidationNameError("backend.contact.errors.validation.name".to_owned());
}
if validator::ValidationErrors::has_error(&Err(value.clone()), "email") {
return Self::ValidationEmailError("backend.contact.errors.validation.email".to_owned());
}
if validator::ValidationErrors::has_error(&Err(value), "message") {
return Self::ValidationMessageError("backend.contact.errors.validation.message".to_owned());
}
Self::ValidationError("backend.contact.errors.validation.other".to_owned())
}
}
/// Converts a `ContactError` into a `ContactResponse`.
///
/// This maps error variants to user-facing error message keys for internationalization.
/// Validation errors map to specific field error keys, while internal errors
/// (settings, email building, SMTP issues) all map to a generic internal error key.
impl From<ContactError> for ContactResponse {
fn from(value: ContactError) -> Self {
Self {
success: false,
message: match value {
ContactError::CouldNotParseRequestEmailAddress(_)
| ContactError::ValidationEmailError(_) => "backend.contact.errors.validation.email",
ContactError::ValidationNameError(_) => "backend.contact.errors.validation.name",
ContactError::ValidationMessageError(_) => "backend.contact.errors.validation.message",
ContactError::CouldNotParseSettingsEmail(_)
| ContactError::FailedToBuildMessage(_)
| ContactError::CouldNotSendEmail(_)
| ContactError::OtherError(_) => "backend.contact.errors.internal",
ContactError::ValidationError(_) => "backend.contact.errors.validation.other",
}
.to_string(),
}
}
}
/// Converts validation errors directly into a `ContactResponse`.
///
/// This is a convenience implementation that first converts `ValidationErrors` to
/// `ContactError`, then converts that to `ContactResponse`. This allows validation
/// errors to be returned directly from handlers as responses.
impl From<ValidationErrors> for ContactResponse {
fn from(value: ValidationErrors) -> Self {
let error: ContactError = value.into();
error.into()
}
}
/// Converts a lettre `AddressError` into a `ContactError`.
///
/// Address parsing errors from lettre are mapped to `CouldNotParseSettingsEmail`
/// as they typically occur when parsing email addresses from application settings.
impl From<AddressError> for ContactError {
fn from(value: AddressError) -> Self {
Self::CouldNotParseSettingsEmail(value.to_string())
}
}
/// Converts a lettre `Error` into a `ContactError`.
///
/// Lettre errors during message construction are mapped to `FailedToBuildMessage`.
/// These errors typically occur when building email messages with invalid headers or content.
impl From<lettre::error::Error> for ContactError {
fn from(value: lettre::error::Error) -> Self {
Self::FailedToBuildMessage(value.to_string())
}
}
impl From<ContactError> for Json<ContactResponse> {
fn from(value: ContactError) -> Self {
let response: ContactResponse = value.into();
response.into()
}
}