DEVELOPMENT ENVIRONMENT

~liljamo/canwa

ffae9608964a8ecce6e2c61ed790b8674e5e1a2b — Jonni Liljamo 16 days ago 59fec95
feat: config defaults
1 files changed, 68 insertions(+), 27 deletions(-)

M src/config.rs
M src/config.rs => src/config.rs +68 -27
@@ 5,14 5,16 @@
 * more information.
 */

use std::{collections::HashMap, net::IpAddr, str::FromStr};
use std::{
    collections::HashMap,
    net::{IpAddr, Ipv4Addr},
};

use serde::Deserialize;
use tokio::{fs::File, io::AsyncReadExt};

#[derive(Debug)]
pub enum ConfigError {
    InvalidInterface,
    NonExistentService(String),
}



@@ 21,7 23,6 @@ impl std::error::Error for ConfigError {}
impl std::fmt::Display for ConfigError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidInterface => write!(f, "invalid interface"),
            Self::NonExistentService(s) => write!(f, "service '{}' does not exist", s),
        }
    }


@@ 58,11 59,27 @@ pub struct NotifierConfig {
    pub services: Vec<String>,
}

fn default_interface() -> IpAddr {
    IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
}

fn default_port() -> u16 {
    3000
}

fn default_hashmap<K, V>() -> HashMap<K, V> {
    HashMap::new()
}

#[derive(Deserialize)]
pub struct Config {
    pub interface: String,
    #[serde(default = "default_interface")]
    pub interface: IpAddr,
    #[serde(default = "default_port")]
    pub port: u16,
    #[serde(default = "default_hashmap")]
    pub services: HashMap<String, Box<dyn ServiceConfig>>,
    #[serde(default = "default_hashmap")]
    pub notifiers: HashMap<String, NotifierConfig>,
}



@@ 70,11 87,6 @@ impl Config {
    pub async fn from_str(s: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let config: Config = toml::from_str(s)?;

        // Verify interface.
        if IpAddr::from_str(&config.interface).is_err() {
            return Err(Box::new(ConfigError::InvalidInterface));
        }

        // Verify notifiers target services that exist.
        for notifier in config.notifiers.values() {
            for service in &notifier.services {


@@ 96,39 108,68 @@ impl Config {

#[cfg(test)]
mod tests {
    use std::net::Ipv6Addr;

    use super::*;

    #[tokio::test]
    async fn basic_ok() {
        assert!(
            Config::from_str(
                r#"
    async fn defaults() {
        let config = Config::from_str("").await.unwrap();

        assert_eq!(config.interface, default_interface());
        assert_eq!(config.port, default_port());
        assert!(config.services.is_empty());
        assert!(config.notifiers.is_empty());
    }

    #[tokio::test]
    async fn set_interface_ipv4() {
        let config = Config::from_str(
            r#"
            interface = "0.0.0.0"
            port = 8080
        "#,
        )
        .await
        .unwrap();

            [services.m]
            type = "matrix"
            instance = ""
            token = ""
            room = ""
        assert_eq!(config.interface, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
    }

            [notifiers.a]
            token = "42"
            services = ["m"]
        "#
            )
            .await
            .is_ok()
    #[tokio::test]
    async fn set_interface_ipv6() {
        let config = Config::from_str(
            r#"
            interface = "2001:42::69"
        "#,
        )
        .await
        .unwrap();

        assert_eq!(
            config.interface,
            IpAddr::V6(Ipv6Addr::new(0x2001, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x69))
        );
    }

    #[tokio::test]
    async fn set_port() {
        let config = Config::from_str(
            r#"
            port = 42
        "#,
        )
        .await
        .unwrap();

        assert_eq!(config.port, 42);
    }

    #[tokio::test]
    async fn invalid_interface() {
        assert!(
            Config::from_str(
                r#"
            interface = "0.0.0.a"
            port = 8080
        "#
            )
            .await