168 lines
7.2 KiB
Rust
168 lines
7.2 KiB
Rust
|
|
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()
|
||
|
|
}
|
||
|
|
}
|