When users submit a contact form, they now receive a confirmation email acknowlledging receipt of their message. The backend also continues to send a notification email to the configured recipient. If the backend fails to send the acknowledgement email to the sender, it will assume the email is not valid and will therefore not transmit the contact request to the configured recipient. Changes: - Refactor `send_email()` to `send_emails()` that sends two emails: - Confirmation email from the submitter - Notification email to the configured recipient - Add `From<T>` implementations of various errors for new error type `ContactError`. - Errors now return a translation identifier for the frontend.
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()
|
|
}
|
|
}
|