rust-eureka 0.2.0

Simple Netflix Eureka Client
Documentation
use serde::de::{Deserialize, Deserializer, Error as DeError, MapAccess, Visitor};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::fmt;

const AMI_LAUNCH_INDEX: &str = "ami-launch-index";
const LOCAL_HOSTNAME: &str = "local-hostname";
const AVAILABILITY_ZONE: &str = "availability-zone";
const INSTANCE_ID: &str = "instance-id";
const PUBLIC_IPV4: &str = "public-ipv4";
const PUBLIC_HOSTNAME: &str = "public-hostname";
const AMI_MANIFEST_PATH: &str = "ami-manifest-path";
const LOCAL_IPV4: &str = "local-ipv4";
const HOSTNAME: &str = "hostname";
const AMI_ID: &str = "ami-id";
const INSTANCE_TYPE: &str = "instance-type";
const JSON_FIELDS: &[&str] = &[
    AMI_LAUNCH_INDEX,
    LOCAL_HOSTNAME,
    AVAILABILITY_ZONE,
    INSTANCE_ID,
    PUBLIC_IPV4,
    PUBLIC_HOSTNAME,
    AMI_MANIFEST_PATH,
    LOCAL_IPV4,
    HOSTNAME,
    AMI_ID,
    INSTANCE_TYPE,
];
const RUST_FIELDS: &[&str] = &[
    "ami_launch_index",
    "local_hostname",
    "availability_zone",
    "instance_id",
    "public_ip4",
    "public_hostname",
    "ami_manifest_path",
    "local_ip4",
    "hostname",
    "ami_id",
    "instance_type",
];
const AMAZON_META_DATA: &str = "AmazonMetaData";

#[derive(Debug, PartialEq)]
pub struct AmazonMetaData {
    pub ami_launch_index: String,
    pub local_hostname: String,
    pub availability_zone: String,
    pub instance_id: String,
    pub public_ip4: String,
    pub public_hostname: String,
    pub ami_manifest_path: String,
    pub local_ip4: String,
    pub hostname: String,
    pub ami_id: String,
    pub instance_type: String,
}

impl Serialize for AmazonMetaData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut s = serializer.serialize_struct(AMAZON_META_DATA, 11)?;
        s.serialize_field(AMI_LAUNCH_INDEX, &self.ami_launch_index)?;
        s.serialize_field(LOCAL_HOSTNAME, &self.local_hostname)?;
        s.serialize_field(AVAILABILITY_ZONE, &self.availability_zone)?;
        s.serialize_field(INSTANCE_ID, &self.instance_id)?;
        s.serialize_field(PUBLIC_IPV4, &self.public_ip4)?;
        s.serialize_field(PUBLIC_HOSTNAME, &self.public_hostname)?;
        s.serialize_field(AMI_MANIFEST_PATH, &self.ami_manifest_path)?;
        s.serialize_field(LOCAL_IPV4, &self.local_ip4)?;
        s.serialize_field(HOSTNAME, &self.hostname)?;
        s.serialize_field(AMI_ID, &self.ami_id)?;
        s.serialize_field(INSTANCE_TYPE, &self.instance_type)?;
        s.end()
    }
}

impl<'de> Deserialize<'de> for AmazonMetaData {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        enum Field {
            AmiLaunchIndex,
            LocalHostname,
            AvailabilityZone,
            InstanceId,
            PublicIp4,
            PublicHostname,
            AmiManifestPath,
            LocalIp4,
            Hostname,
            AmiId,
            InstanceType,
        }

        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                struct FieldVisitor;

                impl<'de> Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                        formatter.write_str("An AmazonMetaData field (see schema)")
                    }

                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
                    where
                        E: DeError,
                    {
                        match v {
                            AMI_LAUNCH_INDEX => Ok(Field::AmiLaunchIndex),
                            LOCAL_HOSTNAME => Ok(Field::LocalHostname),
                            AVAILABILITY_ZONE => Ok(Field::AvailabilityZone),
                            INSTANCE_ID => Ok(Field::InstanceId),
                            PUBLIC_IPV4 => Ok(Field::PublicIp4),
                            PUBLIC_HOSTNAME => Ok(Field::PublicHostname),
                            AMI_MANIFEST_PATH => Ok(Field::AmiManifestPath),
                            LOCAL_IPV4 => Ok(Field::LocalIp4),
                            HOSTNAME => Ok(Field::Hostname),
                            AMI_ID => Ok(Field::AmiId),
                            INSTANCE_TYPE => Ok(Field::InstanceType),
                            _ => Err(DeError::unknown_field(v, JSON_FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct AmazonMetaDataVisitor;

        impl<'de> Visitor<'de> for AmazonMetaDataVisitor {
            type Value = AmazonMetaData;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct AmazonMetaDataVisitor")
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                let mut maybe_ami_launch_index = None;
                let mut maybe_local_hostname = None;
                let mut maybe_availability_zone = None;
                let mut maybe_instance_id = None;
                let mut maybe_public_ip4 = None;
                let mut maybe_public_hostname = None;
                let mut maybe_ami_manifest_path = None;
                let mut maybe_local_ip4 = None;
                let mut maybe_hostname = None;
                let mut maybe_ami_id = None;
                let mut maybe_instance_type = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::AmiLaunchIndex => {
                            if maybe_ami_launch_index.is_some() {
                                return Err(DeError::duplicate_field(AMI_LAUNCH_INDEX));
                            }
                            maybe_ami_launch_index = Some(map.next_value()?)
                        }
                        Field::LocalHostname => {
                            if maybe_local_hostname.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_HOSTNAME));
                            }
                            maybe_local_hostname = Some(map.next_value()?)
                        }
                        Field::AvailabilityZone => {
                            if maybe_availability_zone.is_some() {
                                return Err(DeError::duplicate_field(AVAILABILITY_ZONE));
                            }
                            maybe_availability_zone = Some(map.next_value()?)
                        }
                        Field::InstanceId => {
                            if maybe_instance_id.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_ID));
                            }
                            maybe_instance_id = Some(map.next_value()?)
                        }
                        Field::PublicIp4 => {
                            if maybe_public_ip4.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_IPV4));
                            }
                            maybe_public_ip4 = Some(map.next_value()?)
                        }
                        Field::PublicHostname => {
                            if maybe_public_hostname.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_HOSTNAME));
                            }
                            maybe_public_hostname = Some(map.next_value()?)
                        }
                        Field::AmiManifestPath => {
                            if maybe_ami_manifest_path.is_some() {
                                return Err(DeError::duplicate_field(AMI_MANIFEST_PATH));
                            }
                            maybe_ami_manifest_path = Some(map.next_value()?)
                        }
                        Field::LocalIp4 => {
                            if maybe_local_ip4.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_IPV4));
                            }
                            maybe_local_ip4 = Some(map.next_value()?)
                        }
                        Field::Hostname => {
                            if maybe_hostname.is_some() {
                                return Err(DeError::duplicate_field(HOSTNAME));
                            }
                            maybe_hostname = Some(map.next_value()?)
                        }
                        Field::AmiId => {
                            if maybe_ami_id.is_some() {
                                return Err(DeError::duplicate_field(AMI_ID));
                            }
                            maybe_ami_id = Some(map.next_value()?)
                        }
                        Field::InstanceType => {
                            if maybe_instance_type.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_TYPE));
                            }
                            maybe_instance_type = Some(map.next_value()?)
                        }
                    }
                }

                let ami_launch_index =
                    maybe_ami_launch_index.ok_or_else(|| DeError::missing_field(AMI_LAUNCH_INDEX));
                let local_hostname =
                    maybe_local_hostname.ok_or_else(|| DeError::missing_field(LOCAL_HOSTNAME));
                let availability_zone = maybe_availability_zone
                    .ok_or_else(|| DeError::missing_field(AVAILABILITY_ZONE));
                let instance_id =
                    maybe_instance_id.ok_or_else(|| DeError::missing_field(INSTANCE_ID));
                let public_ip4 =
                    maybe_public_ip4.ok_or_else(|| DeError::missing_field(PUBLIC_IPV4));
                let public_hostname =
                    maybe_public_hostname.ok_or_else(|| DeError::missing_field(PUBLIC_HOSTNAME));
                let ami_manifest_path = maybe_ami_manifest_path
                    .ok_or_else(|| DeError::missing_field(AMI_MANIFEST_PATH));
                let local_ip4 = maybe_local_ip4.ok_or_else(|| DeError::missing_field(LOCAL_IPV4));
                let hostname = maybe_hostname.ok_or_else(|| DeError::missing_field(HOSTNAME));
                let ami_id = maybe_ami_id.ok_or_else(|| DeError::missing_field(AMI_ID));
                let instance_type =
                    maybe_instance_type.ok_or_else(|| DeError::missing_field(INSTANCE_TYPE));

                Ok(AmazonMetaData {
                    ami_launch_index: ami_launch_index?,
                    local_hostname: local_hostname?,
                    availability_zone: availability_zone?,
                    instance_id: instance_id?,
                    public_ip4: public_ip4?,
                    public_hostname: public_hostname?,
                    ami_manifest_path: ami_manifest_path?,
                    local_ip4: local_ip4?,
                    hostname: hostname?,
                    ami_id: ami_id?,
                    instance_type: instance_type?,
                })
            }
        }
        deserializer.deserialize_struct(AMAZON_META_DATA, RUST_FIELDS, AmazonMetaDataVisitor)
    }
}

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

    #[test]
    fn test_serialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string(),
        };
        let json = sample_meta_data();

        let result = serde_json::to_string(&md).unwrap();
        assert_eq!(json, result);
    }

    #[test]
    fn test_deserialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string(),
        };
        let json = sample_meta_data();
        let result = serde_json::from_str(&json).unwrap();
        assert_eq!(md, result);
    }

    pub fn sample_meta_data() -> String {
        r#"{ "ami-launch-index": "001a",
            "local-hostname": "localhost0",
            "availability-zone": "US_East1a",
            "instance-id": "instance1a",
            "public-ipv4": "32.23.21.212",
            "public-hostname": "foo.coma",
            "ami-manifest-path": "/dev/nulla",
            "local-ipv4": "127.0.0.12",
            "hostname": "privatefoo.coma",
            "ami-id": "ami0023",
            "instance-type": "c4xlarged" }"#
            .to_string()
            .replace(" ", "")
            .replace("\n", "")
    }
}