contact-form/src/config.rs
2023-02-26 14:07:06 +01:00

168 lines
4.4 KiB
Rust

use anyhow::{Context, Result};
use serde::Deserialize;
use std::{env, fs::File, io::BufReader};
/// Email server credentials.
#[derive(Deserialize)]
pub struct EmailCredentials {
pub domain: String,
pub username: String,
pub password: String,
}
#[derive(Deserialize)]
pub struct Email {
pub from: String,
pub to: String,
pub credentials: EmailCredentials,
}
/// UTC offset for time formatting.
#[derive(Deserialize)]
pub struct UtcOffset {
pub hours: i8,
pub minutes: i8,
}
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum CustomFieldType {
Text,
Textarea { rows: u8 },
}
#[derive(Deserialize)]
pub struct CustomField {
pub key: String,
pub label: String,
#[serde(default)]
pub required_feedback: Option<String>,
pub field_type: CustomFieldType,
}
/// Error messages for localization.
#[derive(Deserialize)]
pub struct ErrorMessages {
pub captcha_error: String,
pub email_error: String,
}
fn default_name_label() -> String {
"Name".to_string()
}
fn default_name_required_feedback() -> String {
"Please enter your name".to_string()
}
#[derive(Deserialize)]
pub struct NameField {
#[serde(default = "default_name_label")]
pub label: String,
#[serde(default = "default_name_required_feedback")]
pub required_feedback: String,
}
fn default_email_label() -> String {
"Email".to_string()
}
fn default_email_required_feedback() -> String {
"Please enter your email".to_string()
}
#[derive(Deserialize)]
pub struct EmailField {
#[serde(default = "default_email_label")]
pub label: String,
#[serde(default = "default_email_required_feedback")]
pub required_feedback: String,
}
fn default_captcha_label() -> String {
"Enter the code above".to_string()
}
fn default_captcha_required_feedback() -> String {
"Please enter code from the image above".to_string()
}
#[derive(Deserialize)]
pub struct CaptchaField {
#[serde(default = "default_captcha_label")]
pub label: String,
#[serde(default = "default_captcha_required_feedback")]
pub required_feedback: String,
}
/// Localization strings.
#[derive(Deserialize)]
pub struct Strings {
pub description: String,
pub title: String,
pub optional: String,
pub submit: String,
pub success: String,
pub message_from: String,
pub name_field: NameField,
pub email_field: EmailField,
pub captcha_field: CaptchaField,
pub error_messages: ErrorMessages,
}
fn default_lang() -> String {
"en".to_string()
}
#[derive(Deserialize)]
pub struct StateConfig {
/// The path prefix of all routes.
pub path_prefix: String,
pub custom_fields: Vec<CustomField>,
/// The language tag of the HTML file.
#[serde(default = "default_lang")]
pub lang: String,
pub strings: Strings,
}
/// Configuration.
#[derive(Deserialize)]
pub struct Config {
/// The server socket address including port.
pub socket_address: String,
pub email: Email,
pub log_file: String,
pub utc_offset: UtcOffset,
#[serde(flatten)]
pub state_config: StateConfig,
}
impl Config {
/// Parses the configuration from the config path in the environment variable.
pub fn build() -> Result<Self> {
// The environment variable with the path to the config file.
let config_file_var = "CF_CONFIG_FILE";
let config_path = env::var(config_file_var)
.with_context(|| format!("Environment variable {config_file_var} missing!"))?;
let file = File::open(&config_path)
.with_context(|| format!("Can not open the config file at the path {config_path}"))?;
let reader = BufReader::new(file);
let mut config: Self =
serde_yaml::from_reader(reader).context("Can not parse the YAML config file!")?;
// Add a space at the end for the email subject.
config.state_config.strings.message_from =
config.state_config.strings.message_from.trim().to_string() + " ";
// Add the optional word to fields without a required_feedback.
config
.state_config
.custom_fields
.iter_mut()
.filter(|field| field.required_feedback.is_none())
.for_each(|field| {
field.label = format!(
"{} ({})",
field.label.trim(),
config.state_config.strings.optional
);
});
Ok(config)
}
}