coyote 0.1.1

Embeddable ACME server with programmable challenges and storage
Documentation
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
use std::str::FromStr;
use trust_dns_client::rr::Name;

#[derive(Debug, Clone, PartialEq)]
/// DNSName is used to provide a serde interface to DNS names. It is not frequently consumed by
/// external consumers.
pub struct DNSName(pub(crate) Name);

impl DNSName {
    pub(crate) fn from_str(name: &str) -> Result<Self, trust_dns_client::error::ParseError> {
        Ok(Self(Name::from_str(&name)?))
    }

    pub(crate) fn to_string(&self) -> String {
        self.0.to_string()
    }
}

/// serde codec implementation
pub struct DNSNameVisitor;

impl<'de> Visitor<'de> for DNSNameVisitor {
    type Value = DNSName;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("A DNS Name")
    }

    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        match Self::Value::from_str(v) {
            Ok(name) => Ok(name),
            Err(e) => Err(serde::de::Error::custom(e)),
        }
    }
}

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

impl<'de> Deserialize<'de> for DNSName {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(deserializer.deserialize_string(DNSNameVisitor)?)
    }
}

mod tests {
    #[test]
    fn test_dns_serde() {
        use super::DNSName;
        use crate::acme::ACMEIdentifier;
        use spectral::prelude::*;

        let json =
            serde_json::to_string(&ACMEIdentifier::DNS(DNSName::from_str("foo.com").unwrap()));
        assert_that!(json).is_ok();
        let json = json.unwrap();

        let id = serde_json::from_str::<ACMEIdentifier>(&json);
        assert_that!(id).is_ok();
        let id = id.unwrap();
        assert_that!(id.to_string()).is_equal_to("foo.com".to_string());
    }
}