kellnr-settings 5.11.0

Kellnr is a self-hosted registry for Rust crates with support for rustdocs and crates.io caching.
Documentation
use std::fmt::Display;

use serde::{Deserialize, Deserializer, Serialize};

use crate::deserialize_with::DeserializeWith;

#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
#[serde(default)]
pub struct Log {
    #[serde(deserialize_with = "LogFormat::deserialize_with")]
    pub format: LogFormat,
    #[serde(deserialize_with = "LogLevel::deserialize_with")]
    pub level: LogLevel,
    #[serde(deserialize_with = "LogLevel::deserialize_with")]
    pub level_web_server: LogLevel,
}

impl Default for Log {
    fn default() -> Self {
        Self {
            format: LogFormat::Compact,
            level: LogLevel::Info,
            level_web_server: LogLevel::Warn,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum LogFormat {
    Compact,
    Pretty,
    Json,
}

impl DeserializeWith for LogFormat {
    fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(de)?.to_lowercase();

        match s.as_ref() {
            "compact" => Ok(LogFormat::Compact),
            "pretty" => Ok(LogFormat::Pretty),
            "json" => Ok(LogFormat::Json),
            _ => Err(serde::de::Error::custom(
                "error trying to deserialize log format: {s}",
            )),
        }
    }
}

impl Serialize for LogFormat {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl Display for LogFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LogFormat::Compact => write!(f, "compact"),
            LogFormat::Pretty => write!(f, "pretty"),
            LogFormat::Json => write!(f, "json"),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum LogLevel {
    Trace,
    Debug,
    Info,
    Warn,
    Error,
}

impl Display for LogLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LogLevel::Trace => write!(f, "trace"),
            LogLevel::Debug => write!(f, "debug"),
            LogLevel::Info => write!(f, "info"),
            LogLevel::Warn => write!(f, "warn"),
            LogLevel::Error => write!(f, "error"),
        }
    }
}

impl Serialize for LogLevel {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl DeserializeWith for LogLevel {
    fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(de)?.to_lowercase();

        match s.as_ref() {
            "trace" => Ok(LogLevel::Trace),
            "debug" => Ok(LogLevel::Debug),
            "info" => Ok(LogLevel::Info),
            "warn" => Ok(LogLevel::Warn),
            "error" => Ok(LogLevel::Error),
            _ => Err(serde::de::Error::custom(
                "error trying to deserialize log level: {s}",
            )),
        }
    }
}

impl From<LogLevel> for tracing::Level {
    fn from(value: LogLevel) -> Self {
        match value {
            LogLevel::Trace => tracing::Level::TRACE,
            LogLevel::Debug => tracing::Level::DEBUG,
            LogLevel::Info => tracing::Level::INFO,
            LogLevel::Warn => tracing::Level::WARN,
            LogLevel::Error => tracing::Level::ERROR,
        }
    }
}

impl From<LogLevel> for tracing::level_filters::LevelFilter {
    fn from(value: LogLevel) -> Self {
        Self::from_level(value.into())
    }
}

#[cfg(test)]
mod log_format_tests {
    use serde::Deserialize;

    use super::*;

    #[derive(Debug, Deserialize)]
    struct Settings {
        #[serde(deserialize_with = "LogFormat::deserialize_with")]
        log_format: LogFormat,
    }

    #[test]
    fn test_deserialize_log_format_compact() {
        let toml = r#"
            log_format = "compact"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_format, LogFormat::Compact);
    }

    #[test]
    fn test_deserialize_log_format_pretty() {
        let toml = r#"
            log_format = "pretty"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_format, LogFormat::Pretty);
    }

    #[test]
    fn test_deserialize_log_format_json() {
        let toml = r#"
            log_format = "json"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_format, LogFormat::Json);
    }

    #[test]
    fn test_deserialize_log_format_invalid() {
        let toml = r#"
        log_level = "no_log_format"
        "#;

        let settings: Result<Settings, toml::de::Error> = toml::from_str(toml);
        assert!(settings.is_err());
    }
}

#[cfg(test)]
mod log_level_tests {
    use serde::Deserialize;

    use super::*;

    #[derive(Debug, Deserialize)]
    struct Settings {
        #[serde(deserialize_with = "LogLevel::deserialize_with")]
        log_level: LogLevel,
    }

    #[test]
    fn test_deserialize_log_level_trace() {
        let toml = r#"
            log_level = "trace"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_level, LogLevel::Trace);
    }

    #[test]
    fn test_deserialize_log_level_debug() {
        let toml = r#"
            log_level = "debug"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_level, LogLevel::Debug);
    }

    #[test]
    fn test_deserialize_log_level_info() {
        let toml = r#"
            log_level = "info"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_level, LogLevel::Info);
    }

    #[test]
    fn test_deserialize_log_level_warn() {
        let toml = r#"
            log_level = "warn"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_level, LogLevel::Warn);
    }

    #[test]
    fn test_deserialize_log_level_error() {
        let toml = r#"
            log_level = "error"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_level, LogLevel::Error);
    }

    #[test]
    fn test_deserialize_log_level_uppercase() {
        let toml = r#"
        log_level = "DEBUG"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log_level, LogLevel::Debug);
    }

    #[test]
    fn test_deserialize_log_level_invalid() {
        let toml = r#"
        log_level = "no_log_level"
        "#;

        let settings: Result<Settings, toml::de::Error> = toml::from_str(toml);
        assert!(settings.is_err());
    }
}

#[cfg(test)]
mod log_tests {
    use super::*;

    #[derive(Debug, Deserialize)]
    struct Settings {
        pub log: Log,
    }

    #[test]
    fn test_deserialize_whole_log() {
        let toml = r#"
        [no_log]
        foo = "bar"

        [log]
        level = "trace"
        level_web_server = "debug"
        format = "compact"
        "#;

        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.log.level, LogLevel::Trace);
        assert_eq!(settings.log.level_web_server, LogLevel::Debug);
        assert_eq!(settings.log.format, LogFormat::Compact);
    }
}