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