reformulate 0.4.0

reformulate is a standalone server that listens for web form data submissions.
mod mailer;
use mailer::{default_subject_line, send_email};
mod spam;
use spam::{check_for_spam, check_stop_forum_spam};
mod strings;
use env_logger::Env;
use serde::Deserialize;
use strings::{REJECT_MSG, SUCCESS_MSG, VERSION, WELCOME_MSG};
use validator::Validate;
mod mail_template;

use actix_web::{
    error, get,
    middleware::Logger,
    post,
    web::{self, Redirect},
    App, Either, HttpResponse, HttpServer, Responder,
};

fn redirect_default() -> bool {
    false
}
/// Form submission
#[derive(Debug, Deserialize, Validate)]
struct Submission {
    #[serde(alias = "fullname")]
    #[serde(alias = "fullName")]
    full_name: String,
    #[validate(email(
        message = "Invalid email address provided. Please check email and try again."
    ))]
    email: String,
    #[serde(default = "default_subject_line")]
    subject: String,
    message: String,
    #[serde(alias = "site")]
    #[serde(alias = "website")]
    #[serde(alias = "location")]
    from_site: String,
    #[serde(default = "redirect_default")]
    redirect: bool,
}

type FormSubmission = Either<web::Json<Submission>, web::Form<Submission>>;
type FormResponse = Either<HttpResponse, Redirect>;

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body(format!("{WELCOME_MSG}\n{VERSION}"))
}

#[post("/")]
async fn submit(form: FormSubmission) -> Result<FormResponse, error::Error> {
    let (validated, email, full_name, subject, message, from_site, redirect) = match form {
        Either::Left(submission) => (
            submission.validate(),
            submission.email.trim().to_owned(),
            submission.full_name.to_owned(),
            submission.subject.to_owned(),
            submission.message.to_owned(),
            submission.from_site.to_owned(),
            submission.redirect,
        ),
        Either::Right(submission) => (
            submission.validate(),
            submission.email.trim().to_owned(),
            submission.full_name.to_owned(),
            submission.subject.to_owned(),
            submission.message.to_owned(),
            submission.from_site.to_owned(),
            submission.redirect,
        ),
    };

    if let Err(error) = validated {
        Err(error::ErrorBadRequest(error.to_string()))
    } else {
        check_for_spam(&message, REJECT_MSG)?;
        check_stop_forum_spam(&email, REJECT_MSG).await?;

        send_email(&email, &full_name, &subject, &message, &from_site)
            .map(|_| {
                if redirect {
                    Either::Right(Redirect::to(from_site.to_string()))
                } else {
                    Either::Left(HttpResponse::Ok().body(SUCCESS_MSG))
                }
            })
            .map_err(error::ErrorBadRequest)
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(Env::default().default_filter_or("info"));

    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .wrap(Logger::new("%a %{User-Agent}i"))
            .service(hello)
            .service(submit)
    })
    .bind(("0.0.0.0", 8000))?
    .run()
    .await
}