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}