Skip to main content

rust_eureka/request/
amazonmetadata.rs

1use serde::de::{Deserialize, Deserializer, Error as DeError, MapAccess, Visitor};
2use serde::ser::{Serialize, SerializeStruct, Serializer};
3use std::fmt;
4
5const AMI_LAUNCH_INDEX: &str = "ami-launch-index";
6const LOCAL_HOSTNAME: &str = "local-hostname";
7const AVAILABILITY_ZONE: &str = "availability-zone";
8const INSTANCE_ID: &str = "instance-id";
9const PUBLIC_IPV4: &str = "public-ipv4";
10const PUBLIC_HOSTNAME: &str = "public-hostname";
11const AMI_MANIFEST_PATH: &str = "ami-manifest-path";
12const LOCAL_IPV4: &str = "local-ipv4";
13const HOSTNAME: &str = "hostname";
14const AMI_ID: &str = "ami-id";
15const INSTANCE_TYPE: &str = "instance-type";
16const JSON_FIELDS: &[&str] = &[
17    AMI_LAUNCH_INDEX,
18    LOCAL_HOSTNAME,
19    AVAILABILITY_ZONE,
20    INSTANCE_ID,
21    PUBLIC_IPV4,
22    PUBLIC_HOSTNAME,
23    AMI_MANIFEST_PATH,
24    LOCAL_IPV4,
25    HOSTNAME,
26    AMI_ID,
27    INSTANCE_TYPE,
28];
29const RUST_FIELDS: &[&str] = &[
30    "ami_launch_index",
31    "local_hostname",
32    "availability_zone",
33    "instance_id",
34    "public_ip4",
35    "public_hostname",
36    "ami_manifest_path",
37    "local_ip4",
38    "hostname",
39    "ami_id",
40    "instance_type",
41];
42const AMAZON_META_DATA: &str = "AmazonMetaData";
43
44#[derive(Debug, PartialEq)]
45pub struct AmazonMetaData {
46    pub ami_launch_index: String,
47    pub local_hostname: String,
48    pub availability_zone: String,
49    pub instance_id: String,
50    pub public_ip4: String,
51    pub public_hostname: String,
52    pub ami_manifest_path: String,
53    pub local_ip4: String,
54    pub hostname: String,
55    pub ami_id: String,
56    pub instance_type: String,
57}
58
59impl Serialize for AmazonMetaData {
60    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
61    where
62        S: Serializer,
63    {
64        let mut s = serializer.serialize_struct(AMAZON_META_DATA, 11)?;
65        s.serialize_field(AMI_LAUNCH_INDEX, &self.ami_launch_index)?;
66        s.serialize_field(LOCAL_HOSTNAME, &self.local_hostname)?;
67        s.serialize_field(AVAILABILITY_ZONE, &self.availability_zone)?;
68        s.serialize_field(INSTANCE_ID, &self.instance_id)?;
69        s.serialize_field(PUBLIC_IPV4, &self.public_ip4)?;
70        s.serialize_field(PUBLIC_HOSTNAME, &self.public_hostname)?;
71        s.serialize_field(AMI_MANIFEST_PATH, &self.ami_manifest_path)?;
72        s.serialize_field(LOCAL_IPV4, &self.local_ip4)?;
73        s.serialize_field(HOSTNAME, &self.hostname)?;
74        s.serialize_field(AMI_ID, &self.ami_id)?;
75        s.serialize_field(INSTANCE_TYPE, &self.instance_type)?;
76        s.end()
77    }
78}
79
80impl<'de> Deserialize<'de> for AmazonMetaData {
81    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
82    where
83        D: Deserializer<'de>,
84    {
85        enum Field {
86            AmiLaunchIndex,
87            LocalHostname,
88            AvailabilityZone,
89            InstanceId,
90            PublicIp4,
91            PublicHostname,
92            AmiManifestPath,
93            LocalIp4,
94            Hostname,
95            AmiId,
96            InstanceType,
97        }
98
99        impl<'de> Deserialize<'de> for Field {
100            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101            where
102                D: Deserializer<'de>,
103            {
104                struct FieldVisitor;
105
106                impl<'de> Visitor<'de> for FieldVisitor {
107                    type Value = Field;
108
109                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
110                        formatter.write_str("An AmazonMetaData field (see schema)")
111                    }
112
113                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
114                    where
115                        E: DeError,
116                    {
117                        match v {
118                            AMI_LAUNCH_INDEX => Ok(Field::AmiLaunchIndex),
119                            LOCAL_HOSTNAME => Ok(Field::LocalHostname),
120                            AVAILABILITY_ZONE => Ok(Field::AvailabilityZone),
121                            INSTANCE_ID => Ok(Field::InstanceId),
122                            PUBLIC_IPV4 => Ok(Field::PublicIp4),
123                            PUBLIC_HOSTNAME => Ok(Field::PublicHostname),
124                            AMI_MANIFEST_PATH => Ok(Field::AmiManifestPath),
125                            LOCAL_IPV4 => Ok(Field::LocalIp4),
126                            HOSTNAME => Ok(Field::Hostname),
127                            AMI_ID => Ok(Field::AmiId),
128                            INSTANCE_TYPE => Ok(Field::InstanceType),
129                            _ => Err(DeError::unknown_field(v, JSON_FIELDS)),
130                        }
131                    }
132                }
133
134                deserializer.deserialize_identifier(FieldVisitor)
135            }
136        }
137
138        struct AmazonMetaDataVisitor;
139
140        impl<'de> Visitor<'de> for AmazonMetaDataVisitor {
141            type Value = AmazonMetaData;
142
143            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
144                formatter.write_str("struct AmazonMetaDataVisitor")
145            }
146
147            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
148            where
149                A: MapAccess<'de>,
150            {
151                let mut maybe_ami_launch_index = None;
152                let mut maybe_local_hostname = None;
153                let mut maybe_availability_zone = None;
154                let mut maybe_instance_id = None;
155                let mut maybe_public_ip4 = None;
156                let mut maybe_public_hostname = None;
157                let mut maybe_ami_manifest_path = None;
158                let mut maybe_local_ip4 = None;
159                let mut maybe_hostname = None;
160                let mut maybe_ami_id = None;
161                let mut maybe_instance_type = None;
162
163                while let Some(key) = map.next_key()? {
164                    match key {
165                        Field::AmiLaunchIndex => {
166                            if maybe_ami_launch_index.is_some() {
167                                return Err(DeError::duplicate_field(AMI_LAUNCH_INDEX));
168                            }
169                            maybe_ami_launch_index = Some(map.next_value()?)
170                        }
171                        Field::LocalHostname => {
172                            if maybe_local_hostname.is_some() {
173                                return Err(DeError::duplicate_field(LOCAL_HOSTNAME));
174                            }
175                            maybe_local_hostname = Some(map.next_value()?)
176                        }
177                        Field::AvailabilityZone => {
178                            if maybe_availability_zone.is_some() {
179                                return Err(DeError::duplicate_field(AVAILABILITY_ZONE));
180                            }
181                            maybe_availability_zone = Some(map.next_value()?)
182                        }
183                        Field::InstanceId => {
184                            if maybe_instance_id.is_some() {
185                                return Err(DeError::duplicate_field(INSTANCE_ID));
186                            }
187                            maybe_instance_id = Some(map.next_value()?)
188                        }
189                        Field::PublicIp4 => {
190                            if maybe_public_ip4.is_some() {
191                                return Err(DeError::duplicate_field(PUBLIC_IPV4));
192                            }
193                            maybe_public_ip4 = Some(map.next_value()?)
194                        }
195                        Field::PublicHostname => {
196                            if maybe_public_hostname.is_some() {
197                                return Err(DeError::duplicate_field(PUBLIC_HOSTNAME));
198                            }
199                            maybe_public_hostname = Some(map.next_value()?)
200                        }
201                        Field::AmiManifestPath => {
202                            if maybe_ami_manifest_path.is_some() {
203                                return Err(DeError::duplicate_field(AMI_MANIFEST_PATH));
204                            }
205                            maybe_ami_manifest_path = Some(map.next_value()?)
206                        }
207                        Field::LocalIp4 => {
208                            if maybe_local_ip4.is_some() {
209                                return Err(DeError::duplicate_field(LOCAL_IPV4));
210                            }
211                            maybe_local_ip4 = Some(map.next_value()?)
212                        }
213                        Field::Hostname => {
214                            if maybe_hostname.is_some() {
215                                return Err(DeError::duplicate_field(HOSTNAME));
216                            }
217                            maybe_hostname = Some(map.next_value()?)
218                        }
219                        Field::AmiId => {
220                            if maybe_ami_id.is_some() {
221                                return Err(DeError::duplicate_field(AMI_ID));
222                            }
223                            maybe_ami_id = Some(map.next_value()?)
224                        }
225                        Field::InstanceType => {
226                            if maybe_instance_type.is_some() {
227                                return Err(DeError::duplicate_field(INSTANCE_TYPE));
228                            }
229                            maybe_instance_type = Some(map.next_value()?)
230                        }
231                    }
232                }
233
234                let ami_launch_index =
235                    maybe_ami_launch_index.ok_or_else(|| DeError::missing_field(AMI_LAUNCH_INDEX));
236                let local_hostname =
237                    maybe_local_hostname.ok_or_else(|| DeError::missing_field(LOCAL_HOSTNAME));
238                let availability_zone = maybe_availability_zone
239                    .ok_or_else(|| DeError::missing_field(AVAILABILITY_ZONE));
240                let instance_id =
241                    maybe_instance_id.ok_or_else(|| DeError::missing_field(INSTANCE_ID));
242                let public_ip4 =
243                    maybe_public_ip4.ok_or_else(|| DeError::missing_field(PUBLIC_IPV4));
244                let public_hostname =
245                    maybe_public_hostname.ok_or_else(|| DeError::missing_field(PUBLIC_HOSTNAME));
246                let ami_manifest_path = maybe_ami_manifest_path
247                    .ok_or_else(|| DeError::missing_field(AMI_MANIFEST_PATH));
248                let local_ip4 = maybe_local_ip4.ok_or_else(|| DeError::missing_field(LOCAL_IPV4));
249                let hostname = maybe_hostname.ok_or_else(|| DeError::missing_field(HOSTNAME));
250                let ami_id = maybe_ami_id.ok_or_else(|| DeError::missing_field(AMI_ID));
251                let instance_type =
252                    maybe_instance_type.ok_or_else(|| DeError::missing_field(INSTANCE_TYPE));
253
254                Ok(AmazonMetaData {
255                    ami_launch_index: ami_launch_index?,
256                    local_hostname: local_hostname?,
257                    availability_zone: availability_zone?,
258                    instance_id: instance_id?,
259                    public_ip4: public_ip4?,
260                    public_hostname: public_hostname?,
261                    ami_manifest_path: ami_manifest_path?,
262                    local_ip4: local_ip4?,
263                    hostname: hostname?,
264                    ami_id: ami_id?,
265                    instance_type: instance_type?,
266                })
267            }
268        }
269        deserializer.deserialize_struct(AMAZON_META_DATA, RUST_FIELDS, AmazonMetaDataVisitor)
270    }
271}
272
273#[cfg(test)]
274pub mod test {
275    use super::*;
276    use serde_json;
277
278    #[test]
279    fn test_serialize_amazon_meta_data() {
280        let md = AmazonMetaData {
281            ami_launch_index: "001a".to_string(),
282            local_hostname: "localhost0".to_string(),
283            availability_zone: "US_East1a".to_string(),
284            instance_id: "instance1a".to_string(),
285            public_ip4: "32.23.21.212".to_string(),
286            public_hostname: "foo.coma".to_string(),
287            ami_manifest_path: "/dev/nulla".to_string(),
288            local_ip4: "127.0.0.12".to_string(),
289            hostname: "privatefoo.coma".to_string(),
290            ami_id: "ami0023".to_string(),
291            instance_type: "c4xlarged".to_string(),
292        };
293        let json = sample_meta_data();
294
295        let result = serde_json::to_string(&md).unwrap();
296        assert_eq!(json, result);
297    }
298
299    #[test]
300    fn test_deserialize_amazon_meta_data() {
301        let md = AmazonMetaData {
302            ami_launch_index: "001a".to_string(),
303            local_hostname: "localhost0".to_string(),
304            availability_zone: "US_East1a".to_string(),
305            instance_id: "instance1a".to_string(),
306            public_ip4: "32.23.21.212".to_string(),
307            public_hostname: "foo.coma".to_string(),
308            ami_manifest_path: "/dev/nulla".to_string(),
309            local_ip4: "127.0.0.12".to_string(),
310            hostname: "privatefoo.coma".to_string(),
311            ami_id: "ami0023".to_string(),
312            instance_type: "c4xlarged".to_string(),
313        };
314        let json = sample_meta_data();
315        let result = serde_json::from_str(&json).unwrap();
316        assert_eq!(md, result);
317    }
318
319    pub fn sample_meta_data() -> String {
320        r#"{ "ami-launch-index": "001a",
321            "local-hostname": "localhost0",
322            "availability-zone": "US_East1a",
323            "instance-id": "instance1a",
324            "public-ipv4": "32.23.21.212",
325            "public-hostname": "foo.coma",
326            "ami-manifest-path": "/dev/nulla",
327            "local-ipv4": "127.0.0.12",
328            "hostname": "privatefoo.coma",
329            "ami-id": "ami0023",
330            "instance-type": "c4xlarged" }"#
331            .to_string()
332            .replace(" ", "")
333            .replace("\n", "")
334    }
335}