1use serde::ser::{Serialize, Serializer, SerializeStruct};
2use serde::de::{Deserialize, Deserializer, Visitor, Error as DeError, MapAccess};
3use serde_json::{Map, Value};
4use std::fmt;
5use std::str::FromStr;
6use super::DataCenterInfo;
7use super::LeaseInfo;
8use super::Status;
9
10const INSTANCE: &'static str = "Instance";
12const HOST_NAME: &'static str = "hostName";
13const APP: &'static str = "app";
14const IP_ADDR: &'static str = "ipAddr";
15const VIP_ADDRESS: &'static str = "vipAddress";
16const SECURE_VIP_ADDRESS: &'static str = "secureVipAddress";
17const STATUS: &'static str = "status";
18const PORT: &'static str = "port";
19const SECURE_PORT: &'static str = "securePort";
20const HOME_PAGE_URL: &'static str = "homePageUrl";
21const STATUS_PAGE_URL: &'static str = "statusPageUrl";
22const HEALTH_CHECK_URL: &'static str = "healthCheckUrl";
23const DATA_CENTER_INFO: &'static str = "dataCenterInfo";
24const LEASE_INFO: &'static str = "leaseInfo";
25const METADATA: &'static str = "metadata";
26const OVERRIDDENSTATUS: &'static str = "overriddenstatus";
27const COUNTRY_ID: &'static str = "countryId";
28const JSON_FIELDS: &'static [&'static str] = &[INSTANCE, HOST_NAME, APP, IP_ADDR, VIP_ADDRESS, SECURE_VIP_ADDRESS,
29 STATUS, PORT, SECURE_PORT, HOME_PAGE_URL, STATUS_PAGE_URL, HEALTH_CHECK_URL,
30 DATA_CENTER_INFO, LEASE_INFO, METADATA, OVERRIDDENSTATUS, COUNTRY_ID];
31const RUST_FIELDS: &'static [&'static str] = &["host_name", "app", "ip_addr", "vip_address", "secure_vip_address",
32 "status", "port Option", "secure_port", "homepage_url", "status_page_url",
33 "health_check_url", "data_center_info", "lease_info", "metadata", OVERRIDDENSTATUS, "country_id"];
34
35const PORT_DOLLAR: &'static str = "$";
36const PORT_ENABLED: &'static str = "@enabled";
37const PORT_FIELDS: &'static [&'static str] = &[PORT_DOLLAR, PORT_ENABLED];
38
39#[derive(Debug, PartialEq)]
40pub struct Instance {
41 pub host_name: String,
42 pub app: String,
43 pub ip_addr: String,
44 pub vip_address: String,
45 pub secure_vip_address: String,
46 pub status: Status,
47 pub port: Option<u16>,
48 pub secure_port: Option<u16>,
49 pub homepage_url: String,
50 pub status_page_url: String,
51 pub health_check_url: String,
52 pub data_center_info: DataCenterInfo,
53 pub lease_info: Option<LeaseInfo>,
54 pub metadata: Map<String, Value>
55}
56
57struct Port {
58 port: u16
59}
60
61impl Port {
62 fn new(port: u16) -> Port {
63 Port { port: port }
64 }
65}
66
67impl Serialize for Port {
68 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
69 S: Serializer {
70 let mut s = serializer.serialize_struct("Port", 2)?;
71 s.serialize_field(PORT_DOLLAR, &self.port.to_string())?;
72 s.serialize_field(PORT_ENABLED, "true")?;
73 s.end()
74 }
75}
76
77impl<'de> Deserialize<'de> for Port {
78 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
79 D: Deserializer<'de> {
80 enum Field { DollarSign, Enabled };
81
82
83 impl<'de> Deserialize<'de> for Field {
84 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
85 D: Deserializer<'de> {
86 struct FieldVisitor;
87
88 impl<'de> Visitor<'de> for FieldVisitor {
89 type Value = Field;
90
91 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
92 formatter.write_str("'$' or 'enabled'")
93 }
94 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
95 E: DeError, {
96 match v {
97 PORT_DOLLAR => Ok(Field::DollarSign),
98 PORT_ENABLED => Ok(Field::Enabled),
99 _ => Err(DeError::unknown_field(v, PORT_FIELDS))
100 }
101 }
102 }
103 deserializer.deserialize_identifier(FieldVisitor)
104 }
105 }
106
107 struct PortVisitor;
108 impl<'de> Visitor<'de> for PortVisitor {
109 type Value = Port;
110
111 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
112 formatter.write_str("struct Port")
113 }
114
115 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where
116 A: MapAccess<'de>, {
117 let mut maybe_dollar: Option<String> = None;
118 let mut maybe_enabled: Option<String> = None;
119
120 while let Some(key) = map.next_key()? {
121 match key {
122 Field::DollarSign => {
123 if maybe_dollar.is_some() {
124 return Err(DeError::duplicate_field(PORT_DOLLAR));
125 }
126 maybe_dollar = Some(map.next_value()?);
127 }
128 Field::Enabled => {
129 if maybe_enabled.is_some() {
130 return Err(DeError::duplicate_field(PORT_ENABLED));
131 }
132 maybe_enabled = Some(map.next_value()?);
133 }
134 }
135 }
136
137 let dollar = maybe_dollar
138 .map(|s| u16::from_str(s.as_ref()).unwrap())
139 .ok_or_else(|| DeError::missing_field(PORT_DOLLAR))?;
140 maybe_enabled.ok_or_else(|| DeError::missing_field(PORT_ENABLED))?;
141 Ok(Port::new(dollar))
143 }
144 }
145
146 deserializer.deserialize_struct("Port", PORT_FIELDS, PortVisitor)
147 }
148}
149
150impl Serialize for Instance {
151 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
152 S: Serializer {
153 let mut s = serializer.serialize_struct(INSTANCE, 14)?;
154 s.serialize_field(HOST_NAME, &self.host_name)?;
155 s.serialize_field(APP, &self.app)?;
156 s.serialize_field(IP_ADDR, &self.ip_addr)?;
157 s.serialize_field(VIP_ADDRESS, &self.vip_address)?;
158 s.serialize_field(SECURE_VIP_ADDRESS, &self.secure_vip_address)?;
159 s.serialize_field(STATUS, &self.status)?;
160
161 if let &Some(p) = &self.port {
162 let port = Port::new(p);
163 s.serialize_field(PORT, &port)?;
164 }
165
166 if let &Some(p) = &self.secure_port {
167 let port = Port::new(p);
168 s.serialize_field(SECURE_PORT, &port)?;
169 }
170
171 s.serialize_field(HOME_PAGE_URL, &self.homepage_url)?;
172 s.serialize_field(STATUS_PAGE_URL, &self.status_page_url)?;
173 s.serialize_field(HEALTH_CHECK_URL, &self.health_check_url)?;
174 s.serialize_field(DATA_CENTER_INFO, &self.data_center_info)?;
175
176 if let &Some(ref lease_info) = &self.lease_info {
177 s.serialize_field(LEASE_INFO, lease_info)?;
178 }
179
180 if !&self.metadata.is_empty() {
181 s.serialize_field(METADATA, &self.metadata)?;
182 }
183
184 s.end()
185 }
186}
187
188impl<'de> Deserialize<'de> for Instance {
189 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
190 D: Deserializer<'de> {
191 enum Field {
192 HostName,
193 App,
194 IpAddr,
195 VipAddress,
196 SecureVipAddress,
197 Status,
198 Port,
199 SecurePort,
200 HomepageUrl,
201 StatusPageUrl,
202 HealthCheckUrl,
203 DataCenterInfo,
204 LeaseInfo,
205 Metadata
206 }
207
208 impl<'de> Deserialize<'de> for Field {
209 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
210 D: Deserializer<'de> {
211 struct FieldVisitor;
212
213 impl<'de> Visitor<'de> for FieldVisitor {
214 type Value = Field;
215
216 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
217 formatter.write_str("An Instance field (see schema)")
218 }
219 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
220 E: DeError {
221 match v {
222 HOST_NAME => Ok(Field::HostName),
223 APP => Ok(Field::App),
224 IP_ADDR => Ok(Field::IpAddr),
225 VIP_ADDRESS => Ok(Field::VipAddress),
226 SECURE_VIP_ADDRESS => Ok(Field::SecureVipAddress),
227 STATUS => Ok(Field::Status),
228 PORT => Ok(Field::Port),
229 SECURE_PORT => Ok(Field::SecurePort),
230 HOME_PAGE_URL => Ok(Field::HomepageUrl),
231 STATUS_PAGE_URL => Ok(Field::StatusPageUrl),
232 HEALTH_CHECK_URL => Ok(Field::HealthCheckUrl),
233 DATA_CENTER_INFO => Ok(Field::DataCenterInfo),
234 LEASE_INFO => Ok(Field::LeaseInfo),
235 METADATA => Ok(Field::Metadata),
236 _ => Err(DeError::unknown_field(v, JSON_FIELDS))
237 }
238 }
239 }
240
241 deserializer.deserialize_identifier(FieldVisitor)
242 }
243 }
244
245 struct InstanceVisitor;
246
247 impl<'de> Visitor<'de> for InstanceVisitor {
248 type Value = Instance;
249
250 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
251 formatter.write_str("struct Instance")
252 }
253
254 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where
255 A: MapAccess<'de> {
256 let mut maybe_host_name = None;
257 let mut maybe_app = None;
258 let mut maybe_ip_addr = None;
259 let mut maybe_vip_address = None;
260 let mut maybe_secure_vip_address = None;
261 let mut maybe_status = None;
262 let mut maybe_port: Option<Port> = None;
263 let mut maybe_secure_port: Option<Port> = None;
264 let mut maybe_homepage_url = None;
265 let mut maybe_status_page_url = None;
266 let mut maybe_health_check_url = None;
267 let mut maybe_data_center_info = None;
268 let mut maybe_lease_info = None;
269 let mut maybe_metadata = None;
270
271 while let Some(key) = map.next_key()? {
272 match key {
273 Field::HomepageUrl => {
274 if maybe_homepage_url.is_some() {
275 return Err(DeError::duplicate_field(HOME_PAGE_URL));
276 }
277 maybe_homepage_url = Some(map.next_value()?);
278 },
279 Field::App => {
280 if maybe_app.is_some() {
281 return Err(DeError::duplicate_field(APP));
282 }
283 maybe_app = Some(map.next_value()?);
284 },
285 Field::IpAddr => {
286 if maybe_ip_addr.is_some() {
287 return Err(DeError::duplicate_field(IP_ADDR));
288 }
289 maybe_ip_addr = Some(map.next_value()?);
290 },
291 Field::VipAddress => {
292 if maybe_vip_address.is_some() {
293 return Err(DeError::duplicate_field(VIP_ADDRESS));
294 }
295 maybe_vip_address = Some(map.next_value()?);
296 },
297 Field::SecureVipAddress => {
298 if maybe_secure_vip_address.is_some() {
299 return Err(DeError::duplicate_field(SECURE_VIP_ADDRESS));
300 }
301 maybe_secure_vip_address = Some(map.next_value()?);
302 },
303 Field::Status => {
304 if maybe_status.is_some() {
305 return Err(DeError::duplicate_field(STATUS));
306 }
307 maybe_status = Some(map.next_value()?);
308 },
309 Field::Port => {
310 if maybe_port.is_some() {
311 return Err(DeError::duplicate_field(PORT));
312 }
313 maybe_port = Some(map.next_value()?);
314 },
315 Field::SecurePort => {
316 if maybe_secure_port.is_some() {
317 return Err(DeError::duplicate_field(SECURE_PORT));
318 }
319 maybe_secure_port = Some(map.next_value()?);
320 },
321 Field::StatusPageUrl => {
322 if maybe_status_page_url.is_some() {
323 return Err(DeError::duplicate_field(STATUS_PAGE_URL));
324 }
325 maybe_status_page_url = Some(map.next_value()?);
326 },
327 Field::HealthCheckUrl => {
328 if maybe_health_check_url.is_some() {
329 return Err(DeError::duplicate_field(HEALTH_CHECK_URL));
330 }
331 maybe_health_check_url = Some(map.next_value()?);
332 },
333 Field::DataCenterInfo => {
334 if maybe_data_center_info.is_some() {
335 return Err(DeError::duplicate_field(DATA_CENTER_INFO));
336 }
337 maybe_data_center_info = Some(map.next_value()?);
338 },
339 Field::LeaseInfo => {
340 if maybe_lease_info.is_some() {
341 return Err(DeError::duplicate_field(LEASE_INFO));
342 }
343 maybe_lease_info = Some(map.next_value()?);
344 },
345 Field::Metadata => {
346 if maybe_metadata.is_some() {
347 return Err(DeError::duplicate_field(METADATA));
348 }
349 maybe_metadata = Some(map.next_value()?);
350 },
351 Field::HostName => {
352 if maybe_host_name.is_some() {
353 return Err(DeError::duplicate_field(HOST_NAME));
354 }
355 maybe_host_name = Some(map.next_value()?);
356 }
357 }
358 }
359
360 let host_name = maybe_host_name.ok_or_else(|| DeError::missing_field(HOST_NAME));
361 let app = maybe_app.ok_or_else(|| DeError::missing_field(APP));
362 let ip_addr = maybe_ip_addr.ok_or_else(|| DeError::missing_field(IP_ADDR));
363 let vip_address = maybe_vip_address.ok_or_else(|| DeError::missing_field(VIP_ADDRESS));
364 let secure_vip_address = maybe_secure_vip_address.ok_or_else(|| DeError::missing_field(SECURE_VIP_ADDRESS));
365 let status = maybe_status.ok_or_else(|| DeError::missing_field(STATUS));
366 let homepage_url = maybe_homepage_url.ok_or_else(|| DeError::missing_field(HOME_PAGE_URL));
367 let status_page_url = maybe_status_page_url.ok_or_else(|| DeError::missing_field(STATUS_PAGE_URL));
368 let health_check_url = maybe_health_check_url.ok_or_else(|| DeError::missing_field(HEALTH_CHECK_URL));
369 let data_center_info = maybe_data_center_info.ok_or_else(|| DeError::missing_field(DATA_CENTER_INFO));
370 let metadata = maybe_metadata.unwrap_or(Map::new());
371
372 Ok(Instance {
373 host_name: host_name?,
374 app: app?,
375 ip_addr: ip_addr?,
376 vip_address: vip_address?,
377 secure_vip_address: secure_vip_address?,
378 status: status?,
379 port: maybe_port.map(|p| p.port),
380 secure_port: maybe_secure_port.map(|p| p.port),
381 homepage_url: homepage_url?,
382 status_page_url: status_page_url?,
383 health_check_url: health_check_url?,
384 data_center_info: data_center_info?,
385 lease_info: maybe_lease_info,
386 metadata: metadata,
387 })
388 }
389 }
390 deserializer.deserialize_struct(INSTANCE, RUST_FIELDS, InstanceVisitor)
391 }
392}
393
394#[cfg(test)]
395pub mod tests {
396 use super::*;
397 use serde_json;
398 use super::super::DcName;
399 use super::super::AmazonMetaData;
400
401 #[test]
402 fn test_instance_serialization() {
403 let json = build_test_instance_json();
404 let instance = build_test_instance();
405 let result = serde_json::to_string(&instance).unwrap();
406
407 assert_eq!(json, result);
413 }
414
415 #[test]
416 fn test_instance_deserialization() {
417 let json = build_test_instance_json();
418 let instance = build_test_instance();
419 let result = serde_json::from_str(&json).unwrap();
420 assert_eq!(instance, result);
421 }
422
423 pub fn build_test_instance_json() -> String {
424 r#"{
425 "hostName": "Foo",
426 "app": "Bar",
427 "ipAddr": "3.128.2.12",
428 "vipAddress": "127.0.0.1",
429 "secureVipAddress": "127.0.0.2",
430 "status": "UP",
431 "port": { "$": "80", "@enabled": "true" },
432 "securePort": { "$": "443", "@enabled": "true" },
433 "homePageUrl": "http://google.com",
434 "statusPageUrl": "http://nytimes.com",
435 "healthCheckUrl": "http://washingtonpost.com",
436 "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name":"Amazon","metadata":
437 {
438 "ami-launch-index": "001a",
439 "local-hostname": "localhost0",
440 "availability-zone": "US_East1a",
441 "instance-id": "instance1a",
442 "public-ipv4": "32.23.21.212",
443 "public-hostname": "foo.coma",
444 "ami-manifest-path": "/dev/nulla",
445 "local-ipv4": "127.0.0.12",
446 "hostname": "privatefoo.coma",
447 "ami-id": "ami0023",
448 "instance-type": "c4xlarged"
449 }},
450 "leaseInfo": {"evictionDurationInSecs":9600},
451 "metadata": {"something": "somethingelse"}
452 }"#
453 .to_string()
454 .replace(" ", "")
455 .replace("\n", "")
456 }
457
458 pub fn build_test_instance() -> Instance {
459 let mut metadata = Map::new();
460 metadata.insert("something".to_owned(), Value::String("somethingelse".to_owned()));
461 Instance {
462 host_name: "Foo".to_string(),
463 app: "Bar".to_string(),
464 ip_addr: "3.128.2.12".to_string(),
465 vip_address: "127.0.0.1".to_string(),
466 secure_vip_address: "127.0.0.2".to_string(),
467 status: Status::Up,
468 port: Some(80),
469 secure_port: Some(443),
470 homepage_url: "http://google.com".to_string(),
471 status_page_url: "http://nytimes.com".to_string(),
472 health_check_url: "http://washingtonpost.com".to_string(),
473 data_center_info: DataCenterInfo {
474 name: DcName::Amazon,
475 metadata: Some(AmazonMetaData {
476 ami_launch_index: "001a".to_string(),
477 local_hostname: "localhost0".to_string(),
478 availability_zone: "US_East1a".to_string(),
479 instance_id: "instance1a".to_string(),
480 public_ip4: "32.23.21.212".to_string(),
481 public_hostname: "foo.coma".to_string(),
482 ami_manifest_path: "/dev/nulla".to_string(),
483 local_ip4: "127.0.0.12".to_string(),
484 hostname: "privatefoo.coma".to_string(),
485 ami_id: "ami0023".to_string(),
486 instance_type: "c4xlarged".to_string()
487 })
488 },
489 lease_info: Some(LeaseInfo {
490 eviction_duration_in_secs: Some(9600)
491 }),
492 metadata: metadata
493 }
494 }
495}
496