geo302 0.1.1

A simple geoIP-based redirect proxy
use crate::{Continent, Mirror};
use serde::Deserialize;
use std::collections::HashMap;
use std::num::NonZeroU16;
use std::path::Path;
use thiserror::Error;

#[derive(Error, Debug, PartialEq)]
pub enum ConfigError {
    #[error(r#"continents must contain "default""#)]
    NoDefaultContinent,
    #[error(r#"continent {continent:?} mention unknown mirror {mirror}"#)]
    MirrorUnknown {
        continent: Continent,
        mirror: String,
    },
    #[error(r#"continent {0} is not supported, connect Earth goverment to fix it"#)]
    ContinentUnknown(String),
    #[error(r#"no mirrors are specified"#)]
    NoMirrors,
}

#[derive(Deserialize)]
pub struct Config {
    #[serde(default = "Config::default_host")]
    pub host: String,
    #[serde(default = "Config::default_ip_headers")]
    pub ip_headers: Vec<String>,
    #[serde(default = "Config::default_ip_headers_recursive")]
    pub ip_headers_recursive: bool,
    #[serde(default = "Config::default_healthcheck_interval")]
    pub healthckeck_interval: NonZeroU16,
    #[serde(default = "Config::default_log_level")]
    pub log_level: log::Level,
    #[serde(default)]
    pub response_headers: HashMap<String, String>,
    pub geolite2: String,
    pub mirrors: HashMap<String, Mirror>,
    pub continents: HashMap<String, Vec<String>>,
}

impl Config {
    fn default_host() -> String {
        "127.0.0.1:8080".into()
    }

    fn default_ip_headers() -> Vec<String> {
        vec!["X-FORWARDED-FOR".into()]
    }

    fn default_ip_headers_recursive() -> bool {
        true
    }

    fn default_healthcheck_interval() -> NonZeroU16 {
        5.try_into().unwrap()
    }

    fn default_log_level() -> log::Level {
        log::Level::Info
    }
}

pub fn parse_config<P: AsRef<Path>>(path: P) -> anyhow::Result<Config> {
    let toml_string = std::fs::read_to_string(path)?;
    let config: Config = toml::from_str(&toml_string)?;
    Ok(config)
}