nil-zonefile 0.3.0

A library for parsing and creating zonefiles on the new internet.
Documentation
use serde::{Deserialize, Serialize, Deserializer};
use serde::ser::Serializer;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dns {
    pub zone: Option<String>
}

// Function to determine if a Dns should be skipped during serialization
pub fn is_dns_none(dns: &Option<Dns>) -> bool {
    match dns {
        None => true,
        Some(dns) => dns.zone.is_none(),
    }
}

// Implement custom serialization
impl Serialize for Dns {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match &self.zone {
            Some(zone) => serializer.serialize_str(zone),
            None => serializer.serialize_none()
        }
    }
}

// Implement custom deserialization
impl<'de> Deserialize<'de> for Dns {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        // First try to deserialize as a string
        let value = serde::de::DeserializeSeed::deserialize(
            StringOrStruct::default(),
            deserializer
        )?;
        Ok(value)
    }
}

#[derive(Default)]
struct StringOrStruct {
    _private: ()
}

impl<'de> serde::de::DeserializeSeed<'de> for StringOrStruct {
    type Value = Dns;

    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
        D: Deserializer<'de>,
    {
        // Try as string first
        struct StringVisitor;

        impl<'de> serde::de::Visitor<'de> for StringVisitor {
            type Value = Dns;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("string or dns object")
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                Ok(Dns { zone: Some(value.to_string()) })
            }

            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                Ok(Dns { zone: Some(value) })
            }

            fn visit_none<E>(self) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                Ok(Dns { zone: None })
            }

            fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
            where
                D: Deserializer<'de>,
            {
                Deserialize::deserialize(deserializer)
            }

            fn visit_unit<E>(self) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                Ok(Dns { zone: None })
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::MapAccess<'de>,
            {
                let mut zone = None;
                while let Some(key) = map.next_key::<String>()? {
                    if key == "zone" {
                        zone = map.next_value()?;
                    } else {
                        map.next_value::<serde::de::IgnoredAny>()?;
                    }
                }
                Ok(Dns { zone })
            }
        }

        deserializer.deserialize_any(StringVisitor)
    }
}

impl Default for Dns {
    fn default() -> Self {
        Self { zone: None }
    }
}

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

    #[test]
    fn test_dns_serialization() {
        // Test with Some zone
        let dns = Dns {
            zone: Some("example.com".to_string()),
        };
        let json = serde_json::to_string(&dns).unwrap();
        assert_eq!(json, r#""example.com""#);

        // Test with None zone - should serialize as null
        let dns = Dns { zone: None };
        let json = serde_json::to_string(&dns).unwrap();
        assert_eq!(json, "null");

        // Test default - should serialize as null
        let dns = Dns::default();
        let json = serde_json::to_string(&dns).unwrap();
        assert_eq!(json, "null");
    }

    #[test]
    fn test_dns_in_struct() {
        // Test how Dns behaves when it's an optional field in another struct
        #[derive(Serialize)]
        struct Container {
            #[serde(skip_serializing_if = "is_dns_none")]
            dns: Option<Dns>,
            other: String,
        }

        // Test with Some zone
        let container = Container {
            dns: Some(Dns {
                zone: Some("example.com".to_string()),
            }),
            other: "test".to_string(),
        };
        let json = serde_json::to_string(&container).unwrap();
        assert_eq!(json, r#"{"dns":"example.com","other":"test"}"#);

        // Test with None zone - should be omitted entirely
        let container = Container {
            dns: Some(Dns { zone: None }),
            other: "test".to_string(),
        };
        let json = serde_json::to_string(&container).unwrap();
        assert_eq!(json, r#"{"other":"test"}"#);

        // Test with None dns
        let container = Container {
            dns: None,
            other: "test".to_string(),
        };
        let json = serde_json::to_string(&container).unwrap();
        assert_eq!(json, r#"{"other":"test"}"#);
    }

    #[test]
    fn test_dns_deserialization() {
        // Test deserializing from string
        let dns: Dns = serde_json::from_str(r#""example.com""#).unwrap();
        assert_eq!(dns.zone, Some("example.com".to_string()));

        // Test deserializing from null
        let dns: Dns = serde_json::from_str("null").unwrap();
        assert_eq!(dns.zone, None);

        // Test deserializing from empty object
        let dns: Dns = serde_json::from_str("{}").unwrap();
        assert_eq!(dns.zone, None);

        // Test deserializing from object with null zone
        let dns: Dns = serde_json::from_str(r#"{"zone":null}"#).unwrap();
        assert_eq!(dns.zone, None);

        // Test deserializing from object with zone
        let dns: Dns = serde_json::from_str(r#"{"zone":"example.com"}"#).unwrap();
        assert_eq!(dns.zone, Some("example.com".to_string()));
    }
}