use knus::{Decode, DecodeScalar, parse};
use std::{fs::read_to_string, ops::Bound};
#[derive(Decode)]
pub struct Config {
#[knus(children(name = "server"))]
pub servers: Vec<Server>,
}
#[derive(Decode, Clone)]
pub struct Settings {
#[knus(child, unwrap(argument))]
pub payload_limit: Option<u64>,
}
#[derive(Decode)]
pub struct Server {
#[knus(children(name = "listen"))]
pub listen: Vec<Bind>,
#[knus(children(name = "host"))]
pub host: Vec<ConfigHost>,
#[knus(child)]
pub auto: Option<Auto>,
#[knus(child)]
pub settings: Option<Settings>,
}
#[derive(Decode)]
pub struct ConfigHost {
#[knus(children(name = "name"))]
pub name: Vec<HostName>,
#[knus(children(name = "path"))]
pub path: Vec<Path>,
#[knus(children(name = "proxy"))]
pub proxy: Vec<Proxy>,
#[knus(children(name = "log"))]
pub log: Vec<Log>,
}
#[derive(Decode)]
pub struct Auto {
#[knus(argument)]
base_path: String,
#[knus(child, unwrap(argument), default)]
pub list: bool,
#[knus(children(name = "header"))]
pub headers: Vec<Header>,
#[knus(child, unwrap(argument))]
pub index: Option<String>,
}
impl Auto {
pub fn base_path(&self) -> &str {
&self.base_path
}
}
#[derive(Decode)]
pub struct Bind {
#[knus(argument)]
sock_addr: String,
#[knus(child)]
ssl: Option<SSL>,
}
impl Bind {
pub fn sock_addr(&self) -> String {
self.sock_addr.clone()
}
pub fn ssl(&self) -> &Option<SSL> {
&self.ssl
}
}
#[derive(Decode)]
pub struct SSL {
#[knus(children(name = "cert"))]
pub certs: Vec<Certs>,
#[knus(child, unwrap(argument))]
pub letsencrypt: Option<String>,
#[knus(child, unwrap(argument))]
pub acme_redirect: Option<String>,
#[knus(child)]
pub acme: Option<SSLAcme>,
}
#[derive(Decode)]
pub struct SSLAcme {
#[knus(child, unwrap(arguments))]
contacts: Vec<String>,
#[knus(child, unwrap(argument))]
store: String,
#[knus(child, unwrap(argument))]
provider: Option<String>,
#[knus(child, unwrap(arguments))]
extra_domains: Option<Vec<String>>,
}
impl SSLAcme {
pub fn contacts(&self) -> &Vec<String> {
&self.contacts
}
pub fn store(&self) -> &String {
&self.store
}
pub fn provider(&self) -> &Option<String> {
&self.provider
}
pub fn extra_domains(&self) -> &Option<Vec<String>> {
&self.extra_domains
}
}
#[derive(Decode, Clone)]
pub struct Certs {
#[knus(child, unwrap(argument))]
pub cert: String,
#[knus(child, unwrap(argument))]
pub key: String,
}
impl Certs {
pub fn new(cert: &str, key: &str) -> Self {
Self {
cert: cert.to_owned(),
key: key.to_owned(),
}
}
}
#[derive(Decode, Clone)]
pub struct HostName(#[knus(argument)] pub String);
impl HostName {
pub fn value(&self) -> String {
self.0.clone()
}
}
#[derive(Decode, Clone, Debug)]
pub struct Path {
#[knus(argument)]
pub path: String,
#[knus(child, unwrap(argument))]
pub location: String,
#[knus(child, unwrap(argument), default)]
pub list: bool,
#[knus(children(name = "header"))]
pub headers: Vec<Header>,
#[knus(child, unwrap(argument))]
pub index: Option<String>,
#[knus(children(name = "pre_compression"))]
pub compressions: Vec<PreCompressed>,
}
impl Path {
#[cfg(test)]
pub(crate) fn new(path: &str, location: &str) -> Self {
Self {
path: path.to_owned(),
location: location.to_owned(),
list: false,
headers: Default::default(),
index: None,
compressions: Default::default(),
}
}
pub(crate) fn strip_prefix(&mut self, prefix: &str) {
if let Some(new_value) = self.path.strip_prefix(&prefix) {
self.path = new_value.to_owned();
}
}
}
#[derive(DecodeScalar, Clone, Debug)]
pub enum PreCompType {
Gzip,
Brotli,
Zstd,
None,
}
#[derive(Decode, Clone, Debug)]
pub struct PreCompressed {
#[knus(argument)]
pub(crate) compression_type: PreCompType,
#[knus(argument)]
pub(crate) extension: String,
}
impl PreCompressed {
pub fn defaults() -> Vec<Self> {
let mut defs = Vec::new();
defs.push(Self::new(PreCompType::Gzip, ".gz"));
defs.push(Self::new(PreCompType::Brotli, ".br"));
defs.push(Self::new(PreCompType::Zstd, ".zst"));
defs.push(Self::new(PreCompType::None, ""));
defs
}
fn new(t: PreCompType, extension: &str) -> Self {
PreCompressed {
compression_type: t,
extension: extension.to_owned(),
}
}
}
#[derive(Decode, Clone, Debug)]
pub struct Header {
#[knus(argument)]
name: String,
#[knus(argument)]
value: String,
}
impl Header {
pub fn name(&self) -> &str {
&self.name
}
pub fn value(&self) -> &str {
&self.value
}
}
#[derive(Decode, Clone)]
pub struct Proxy {
#[knus(argument)]
pub path: String,
#[knus(child, unwrap(argument))]
pub url: String,
}
#[derive(DecodeScalar, Clone, Default)]
pub enum LogLevel {
Trace,
Debug,
#[default]
Info,
Warn,
Error,
}
#[derive(Decode, Clone)]
pub struct Log {
#[knus(argument, default = 100)]
status_from: u16,
#[knus(argument, default = 600)]
status_to: u16,
#[knus(property)]
level: LogLevel,
#[knus(child, unwrap(argument),default="%a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T".into())]
format: String,
}
impl Log {
pub fn nothing() -> Self {
Self {
status_from: 600,
status_to: 600,
level: LogLevel::Trace,
format: "".to_owned(),
}
}
pub fn status_range(&self) -> (Bound<u16>, Bound<u16>) {
(Bound::Included(self.status_from), Bound::Excluded(self.status_to))
}
pub fn format(&self) -> &str {
&self.format
}
pub fn level(&self) -> &LogLevel {
&self.level
}
}
impl Config {
pub fn read<P: AsRef<std::path::Path>>(path: P) -> std::result::Result<Self, Box<dyn std::error::Error>> {
let pn = path.as_ref().to_string_lossy();
let text = read_to_string(&path)?;
Ok(parse(&pn, &text)?)
}
pub fn from_str(text: &str) -> std::result::Result<Self, Box<dyn std::error::Error>> {
Ok(parse("", text)?)
}
}
#[cfg(test)]
mod tests {
use super::Config;
#[test]
fn test_simple_reading() {
let read = Config::read("./fixtures/simple.kdl").unwrap();
assert_eq!(read.servers[0].host[0].name[0].0, "tglman.com");
assert_eq!(read.servers[0].host[0].name[1].0, "www.tglman.com");
assert_eq!(
read.servers[0].listen[3]
.ssl
.as_ref()
.unwrap()
.acme
.as_ref()
.unwrap()
.extra_domains
.as_ref()
.unwrap()[0],
"may.example.com"
);
}
}