use headers::HeaderMap;
use serde::Deserialize;
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::path::Path;
use std::{collections::BTreeSet, path::PathBuf};
use crate::directory_listing::DirListFmt;
use crate::{helpers, Context, Result};
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl LogLevel {
pub fn name(&self) -> &'static str {
match self {
LogLevel::Error => "error",
LogLevel::Warn => "warn",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
LogLevel::Trace => "trace",
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Headers {
pub source: String,
#[serde(rename(deserialize = "headers"), with = "http_serde::header_map")]
pub headers: HeaderMap,
}
#[derive(Debug, Serialize_repr, Deserialize_repr, Clone)]
#[repr(u16)]
pub enum RedirectsKind {
Permanent = 301,
Temporary = 302,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Redirects {
pub source: String,
pub destination: String,
pub kind: RedirectsKind,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Rewrites {
pub source: String,
pub destination: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Advanced {
pub headers: Option<Vec<Headers>>,
pub rewrites: Option<Vec<Rewrites>>,
pub redirects: Option<Vec<Redirects>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct General {
pub host: Option<String>,
pub port: Option<u16>,
pub root: Option<PathBuf>,
pub log_level: Option<LogLevel>,
pub cache_control_headers: Option<bool>,
pub compression: Option<bool>,
pub compression_static: Option<bool>,
pub page404: Option<PathBuf>,
pub page50x: Option<PathBuf>,
pub http2: Option<bool>,
pub http2_tls_cert: Option<PathBuf>,
pub http2_tls_key: Option<PathBuf>,
pub security_headers: Option<bool>,
pub cors_allow_origins: Option<String>,
pub cors_allow_headers: Option<String>,
pub cors_expose_headers: Option<String>,
pub directory_listing: Option<bool>,
pub directory_listing_order: Option<u8>,
pub directory_listing_format: Option<DirListFmt>,
pub basic_auth: Option<String>,
pub fd: Option<usize>,
pub threads_multiplier: Option<usize>,
pub grace_period: Option<u8>,
pub page_fallback: Option<PathBuf>,
pub log_remote_address: Option<bool>,
pub redirect_trailing_slash: Option<bool>,
#[cfg(windows)]
pub windows_service: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Settings {
pub general: Option<General>,
pub advanced: Option<Advanced>,
}
impl Settings {
pub fn read(config_file: &Path) -> Result<Settings> {
let ext = config_file.extension();
if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") {
bail!("configuration file should be in toml format. E.g `config.toml`");
}
let toml =
read_toml_file(config_file).with_context(|| "error reading toml configuration file")?;
let mut unused = BTreeSet::new();
let manifest: Settings = serde_ignored::deserialize(toml, |path| {
let mut key = String::new();
helpers::stringify(&mut key, &path);
unused.insert(key);
})
.with_context(|| "error during toml configuration file deserialization")?;
for key in unused {
println!(
"Warning: unused configuration manifest key \"{}\" or unsupported",
key
);
}
Ok(manifest)
}
}
fn read_toml_file(path: &Path) -> Result<toml::Value> {
let toml_str = helpers::read_file(path).with_context(|| {
format!(
"error trying to deserialize toml configuration file at \"{}\"",
path.display()
)
})?;
let first_error = match toml_str.parse() {
Ok(res) => return Ok(res),
Err(err) => err,
};
let mut second_parser = toml::de::Deserializer::new(&toml_str);
second_parser.set_require_newline_after_table(false);
if let Ok(res) = toml::Value::deserialize(&mut second_parser) {
let msg = format!(
"\
TOML file found which contains invalid syntax and will soon not parse
at `{}`.
The TOML spec requires newlines after table definitions (e.g., `[a] b = 1` is
invalid), but this file has a table header which does not have a newline after
it. A newline needs to be added and this warning will soon become a hard error
in the future.",
path.display()
);
println!("{}", &msg);
return Ok(res);
}
let first_error = anyhow::Error::from(first_error);
Err(first_error.context("could not parse data input as toml format"))
}