apollo-configuration 0.4.0

A typed, friendly configuration system for Apollo products.
Documentation
//! Support for [`std::time::Duration`] in configuration.

use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;

use derive_more::{Deref, From};
use humantime_serde::re::humantime;
use schemars::JsonSchema;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize};

use crate::Validate;

/// A duration parsed from human-readable format in configuration.
///
/// Supports formats like `30s`, `5m`, `1h30m`, `100ms`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deref, From)]
pub struct Duration(std::time::Duration);

impl FromStr for Duration {
    type Err = humantime::DurationError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        humantime::parse_duration(s).map(Self)
    }
}

impl fmt::Display for Duration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", humantime::format_duration(self.0))
    }
}

impl<'de> Deserialize<'de> for Duration {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        humantime::parse_duration(&s)
            .map(Self)
            .map_err(serde::de::Error::custom)
    }
}

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

impl JsonSchema for Duration {
    fn schema_name() -> Cow<'static, str> {
        Cow::Borrowed("Duration")
    }

    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
        String::json_schema(generator)
    }
}

impl Validate for Duration {}

#[cfg(test)]
mod tests {
    use super::Duration;
    use std::str::FromStr as _;

    #[test]
    fn parse_from_str() {
        assert_eq!(
            Duration::from_str("5m 20s")
                .expect("should parse")
                .as_secs(),
            320
        );
        let _err = Duration::from_str("5q").expect_err("should not parse");
    }
}