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;
9use super::ActionType;
10
11const INSTANCE: &'static str = "Instance";
13const HOST_NAME: &'static str = "hostName";
14const APP: &'static str = "app";
15const IP_ADDR: &'static str = "ipAddr";
16const VIP_ADDRESS: &'static str = "vipAddress";
17const SECURE_VIP_ADDRESS: &'static str = "secureVipAddress";
18const STATUS: &'static str = "status";
19const PORT: &'static str = "port";
20const SECURE_PORT: &'static str = "securePort";
21const HOME_PAGE_URL: &'static str = "homePageUrl";
22const STATUS_PAGE_URL: &'static str = "statusPageUrl";
23const HEALTH_CHECK_URL: &'static str = "healthCheckUrl";
24const DATA_CENTER_INFO: &'static str = "dataCenterInfo";
25const LEASE_INFO: &'static str = "leaseInfo";
26const METADATA: &'static str = "metadata";
27const OVERRIDDENSTATUS: &'static str = "overriddenstatus";
28const COUNTRY_ID: &'static str = "countryId";
29const LAST_UPDATED_TIMESTAMP: &'static str = "lastUpdatedTimestamp";
30const LAST_DIRTY_TIMESTAMP: &'static str = "lastDirtyTimestamp";
31const ACTION_TYPE: &'static str = "actionType";
32const IS_COORDINATED_DISCOVERY_SERVER: &'static str = "isCoordinatingDiscoveryServer";
33const JSON_FIELDS: &'static [&'static str] = &[INSTANCE, HOST_NAME, APP, IP_ADDR, VIP_ADDRESS, SECURE_VIP_ADDRESS,
34 STATUS, PORT, SECURE_PORT, HOME_PAGE_URL, STATUS_PAGE_URL, HEALTH_CHECK_URL,
35 DATA_CENTER_INFO, LEASE_INFO, METADATA, OVERRIDDENSTATUS, COUNTRY_ID, LAST_UPDATED_TIMESTAMP, LAST_DIRTY_TIMESTAMP,
36 ACTION_TYPE, IS_COORDINATED_DISCOVERY_SERVER];
37const RUST_FIELDS: &'static [&'static str] = &["host_name", "app", "ip_addr", "vip_address", "secure_vip_address",
38 "status", "port Option", "secure_port", "homepage_url", "status_page_url",
39 "health_check_url", "data_center_info", "lease_info", "metadata", OVERRIDDENSTATUS, "country_id", "last_updated_timestamp",
40 "last_dirty_timestamp", "action_type", "is_coordinating_discovery_server"];
41
42const PORT_DOLLAR: &'static str = "$";
43const PORT_ENABLED: &'static str = "@enabled";
44const PORT_FIELDS: &'static [&'static str] = &[PORT_DOLLAR, PORT_ENABLED];
45
46#[derive(Debug, PartialEq)]
47pub struct Instance {
48 pub host_name: String,
49 pub app: String,
50 pub ip_addr: String,
51 pub vip_address: String,
52 pub secure_vip_address: String,
53 pub status: Status,
54 pub port: Option<u16>,
55 pub secure_port: Option<u16>,
56 pub homepage_url: String,
57 pub status_page_url: String,
58 pub health_check_url: String,
59 pub data_center_info: DataCenterInfo,
60 pub lease_info: Option<LeaseInfo>,
61 pub metadata: Map<String, Value>,
62 pub overriddenstatus: Option<Status>,
63 pub country_id: u16,
64 pub last_updated_timestamp: i64,
65 pub last_dirty_timestamp: i64,
66 pub action_type: ActionType,
67 pub is_coordinating_discovery_server: bool
68}
69
70struct Port {
71 port: u16
72}
73
74impl Port {
75 fn new(port: u16) -> Port {
76 Port { port: port }
77 }
78}
79
80impl Serialize for Port {
81 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
82 S: Serializer {
83 let mut s = serializer.serialize_struct("Port", 2)?;
84 s.serialize_field(PORT_DOLLAR, &self.port.to_string())?;
85 s.serialize_field(PORT_ENABLED, "true")?;
86 s.end()
87 }
88}
89
90impl<'de> Deserialize<'de> for Port {
91 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
92 D: Deserializer<'de> {
93 enum Field { DollarSign, Enabled };
94
95
96 impl<'de> Deserialize<'de> for Field {
97 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
98 D: Deserializer<'de> {
99 struct FieldVisitor;
100
101 impl<'de> Visitor<'de> for FieldVisitor {
102 type Value = Field;
103
104 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
105 formatter.write_str("'$' or 'enabled'")
106 }
107 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
108 E: DeError, {
109 match v {
110 PORT_DOLLAR => Ok(Field::DollarSign),
111 PORT_ENABLED => Ok(Field::Enabled),
112 _ => Err(DeError::unknown_field(v, PORT_FIELDS))
113 }
114 }
115 }
116 deserializer.deserialize_identifier(FieldVisitor)
117 }
118 }
119
120 struct PortVisitor;
121 impl<'de> Visitor<'de> for PortVisitor {
122 type Value = Port;
123
124 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
125 formatter.write_str("struct Port")
126 }
127
128 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where
129 A: MapAccess<'de>, {
130 let mut maybe_dollar: Option<String> = None;
131 let mut maybe_enabled: Option<String> = None;
132
133 while let Some(key) = map.next_key()? {
134 match key {
135 Field::DollarSign => {
136 if maybe_dollar.is_some() {
137 return Err(DeError::duplicate_field(PORT_DOLLAR));
138 }
139 maybe_dollar = Some(map.next_value()?);
140 }
141 Field::Enabled => {
142 if maybe_enabled.is_some() {
143 return Err(DeError::duplicate_field(PORT_ENABLED));
144 }
145 maybe_enabled = Some(map.next_value()?);
146 }
147 }
148 }
149
150 let dollar = maybe_dollar
151 .map(|s| u16::from_str(s.as_ref()).unwrap())
152 .ok_or_else(|| DeError::missing_field(PORT_DOLLAR))?;
153 maybe_enabled.ok_or_else(|| DeError::missing_field(PORT_ENABLED))?;
154 Ok(Port::new(dollar))
156 }
157 }
158
159 deserializer.deserialize_struct("Port", PORT_FIELDS, PortVisitor)
160 }
161}
162
163impl Serialize for Instance {
164 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
165 S: Serializer {
166 let mut s = serializer.serialize_struct(INSTANCE, 14)?;
167 s.serialize_field(HOST_NAME, &self.host_name)?;
168 s.serialize_field(APP, &self.app)?;
169 s.serialize_field(IP_ADDR, &self.ip_addr)?;
170 s.serialize_field(VIP_ADDRESS, &self.vip_address)?;
171 s.serialize_field(SECURE_VIP_ADDRESS, &self.secure_vip_address)?;
172 s.serialize_field(STATUS, &self.status)?;
173
174 if let &Some(p) = &self.port {
175 let port = Port::new(p);
176 s.serialize_field(PORT, &port)?;
177 }
178
179 if let &Some(p) = &self.secure_port {
180 let port = Port::new(p);
181 s.serialize_field(SECURE_PORT, &port)?;
182 }
183
184 s.serialize_field(HOME_PAGE_URL, &self.homepage_url)?;
185 s.serialize_field(STATUS_PAGE_URL, &self.status_page_url)?;
186 s.serialize_field(HEALTH_CHECK_URL, &self.health_check_url)?;
187 s.serialize_field(DATA_CENTER_INFO, &self.data_center_info)?;
188
189 if let &Some(ref lease_info) = &self.lease_info {
190 s.serialize_field(LEASE_INFO, lease_info)?;
191 }
192
193 if !&self.metadata.is_empty() {
194 s.serialize_field(METADATA, &self.metadata)?;
195 }
196
197 s.serialize_field(COUNTRY_ID, &self.country_id)?;
198
199 if let &Some(ref overridenstatus) = &self.overriddenstatus {
200 s.serialize_field(OVERRIDDENSTATUS, overridenstatus)?;
201 }
202
203 s.serialize_field(IS_COORDINATED_DISCOVERY_SERVER, &self.is_coordinating_discovery_server)?;
204 s.serialize_field(LAST_UPDATED_TIMESTAMP, &self.last_updated_timestamp)?;
205 s.serialize_field(LAST_DIRTY_TIMESTAMP, &self.last_dirty_timestamp)?;
206 s.serialize_field(ACTION_TYPE, &self.action_type)?;
207
208 s.end()
209 }
210}
211
212impl<'de> Deserialize<'de> for Instance {
213 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
214 D: Deserializer<'de> {
215 enum Field {
216 HostName,
217 App,
218 IpAddr,
219 VipAddress,
220 SecureVipAddress,
221 Status,
222 Port,
223 SecurePort,
224 HomepageUrl,
225 StatusPageUrl,
226 HealthCheckUrl,
227 DataCenterInfo,
228 LeaseInfo,
229 Metadata,
230 Overriddenstatus,
231 CountryId,
232 LastUpdatedTimestamp,
233 LastDirtyTimestamp,
234 IsCoordinatingDiscoveryServer,
235 ActionType
236 }
237
238 impl<'de> Deserialize<'de> for Field {
239 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
240 D: Deserializer<'de> {
241 struct FieldVisitor;
242
243 impl<'de> Visitor<'de> for FieldVisitor {
244 type Value = Field;
245
246 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
247 formatter.write_str("An Instance field (see schema)")
248 }
249 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
250 E: DeError {
251 match v {
252 HOST_NAME => Ok(Field::HostName),
253 APP => Ok(Field::App),
254 IP_ADDR => Ok(Field::IpAddr),
255 VIP_ADDRESS => Ok(Field::VipAddress),
256 SECURE_VIP_ADDRESS => Ok(Field::SecureVipAddress),
257 STATUS => Ok(Field::Status),
258 PORT => Ok(Field::Port),
259 SECURE_PORT => Ok(Field::SecurePort),
260 HOME_PAGE_URL => Ok(Field::HomepageUrl),
261 STATUS_PAGE_URL => Ok(Field::StatusPageUrl),
262 HEALTH_CHECK_URL => Ok(Field::HealthCheckUrl),
263 DATA_CENTER_INFO => Ok(Field::DataCenterInfo),
264 LEASE_INFO => Ok(Field::LeaseInfo),
265 METADATA => Ok(Field::Metadata),
266 OVERRIDDENSTATUS => Ok(Field::Overriddenstatus),
267 COUNTRY_ID => Ok(Field::CountryId),
268 LAST_UPDATED_TIMESTAMP => Ok(Field::LastUpdatedTimestamp),
269 LAST_DIRTY_TIMESTAMP => Ok(Field::LastDirtyTimestamp),
270 IS_COORDINATED_DISCOVERY_SERVER => Ok(Field::IsCoordinatingDiscoveryServer),
271 ACTION_TYPE => Ok(Field::ActionType),
272 _ => Err(DeError::unknown_field(v, JSON_FIELDS))
273 }
274 }
275 }
276
277 deserializer.deserialize_identifier(FieldVisitor)
278 }
279 }
280
281 struct InstanceVisitor;
282
283 impl<'de> Visitor<'de> for InstanceVisitor {
284 type Value = Instance;
285
286 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
287 formatter.write_str("struct Instance")
288 }
289
290 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where
291 A: MapAccess<'de> {
292 let mut maybe_host_name = None;
293 let mut maybe_app = None;
294 let mut maybe_ip_addr = None;
295 let mut maybe_vip_address = None;
296 let mut maybe_secure_vip_address = None;
297 let mut maybe_status = None;
298 let mut maybe_port: Option<Port> = None;
299 let mut maybe_secure_port: Option<Port> = None;
300 let mut maybe_homepage_url = None;
301 let mut maybe_status_page_url = None;
302 let mut maybe_health_check_url = None;
303 let mut maybe_data_center_info = None;
304 let mut maybe_lease_info = None;
305 let mut maybe_metadata: Option<Map<String, Value>> = None;
306 let mut maybe_overriddenstatus = None;
307 let mut maybe_country_id = None;
308 let mut maybe_last_updated_timestamp = None;
309 let mut maybe_last_dirty_timestamp = None;
310 let mut maybe_is_coordinating_discovery_server = None;
311 let mut maybe_action_type = None;
312
313 while let Some(key) = map.next_key()? {
314 match key {
315 Field::HomepageUrl => {
316 if maybe_homepage_url.is_some() {
317 return Err(DeError::duplicate_field(HOME_PAGE_URL));
318 }
319 maybe_homepage_url = Some(map.next_value()?);
320 },
321 Field::App => {
322 if maybe_app.is_some() {
323 return Err(DeError::duplicate_field(APP));
324 }
325 maybe_app = Some(map.next_value()?);
326 },
327 Field::IpAddr => {
328 if maybe_ip_addr.is_some() {
329 return Err(DeError::duplicate_field(IP_ADDR));
330 }
331 maybe_ip_addr = Some(map.next_value()?);
332 },
333 Field::VipAddress => {
334 if maybe_vip_address.is_some() {
335 return Err(DeError::duplicate_field(VIP_ADDRESS));
336 }
337 maybe_vip_address = Some(map.next_value()?);
338 },
339 Field::SecureVipAddress => {
340 if maybe_secure_vip_address.is_some() {
341 return Err(DeError::duplicate_field(SECURE_VIP_ADDRESS));
342 }
343 maybe_secure_vip_address = Some(map.next_value()?);
344 },
345 Field::Status => {
346 if maybe_status.is_some() {
347 return Err(DeError::duplicate_field(STATUS));
348 }
349 maybe_status = Some(map.next_value()?);
350 },
351 Field::Port => {
352 if maybe_port.is_some() {
353 return Err(DeError::duplicate_field(PORT));
354 }
355 maybe_port = Some(map.next_value()?);
356 },
357 Field::SecurePort => {
358 if maybe_secure_port.is_some() {
359 return Err(DeError::duplicate_field(SECURE_PORT));
360 }
361 maybe_secure_port = Some(map.next_value()?);
362 },
363 Field::StatusPageUrl => {
364 if maybe_status_page_url.is_some() {
365 return Err(DeError::duplicate_field(STATUS_PAGE_URL));
366 }
367 maybe_status_page_url = Some(map.next_value()?);
368 },
369 Field::HealthCheckUrl => {
370 if maybe_health_check_url.is_some() {
371 return Err(DeError::duplicate_field(HEALTH_CHECK_URL));
372 }
373 maybe_health_check_url = Some(map.next_value()?);
374 },
375 Field::DataCenterInfo => {
376 if maybe_data_center_info.is_some() {
377 return Err(DeError::duplicate_field(DATA_CENTER_INFO));
378 }
379 maybe_data_center_info = Some(map.next_value()?);
380 },
381 Field::LeaseInfo => {
382 if maybe_lease_info.is_some() {
383 return Err(DeError::duplicate_field(LEASE_INFO));
384 }
385 maybe_lease_info = Some(map.next_value()?);
386 },
387 Field::Metadata => {
388 if maybe_metadata.is_some() {
389 return Err(DeError::duplicate_field(METADATA));
390 }
391 maybe_metadata = Some(map.next_value()?);
392 },
393 Field::HostName => {
394 if maybe_host_name.is_some() {
395 return Err(DeError::duplicate_field(HOST_NAME));
396 }
397 maybe_host_name = Some(map.next_value()?);
398 },
399 Field::Overriddenstatus => {
400 if maybe_overriddenstatus.is_some() {
401 return Err(DeError::duplicate_field(OVERRIDDENSTATUS));
402 }
403 maybe_overriddenstatus = Some(map.next_value()?);
404 },
405 Field::CountryId => {
406 if maybe_country_id.is_some() {
407 return Err(DeError::duplicate_field(COUNTRY_ID));
408 }
409 maybe_country_id = Some(map.next_value()?);
410 },
411 Field::LastUpdatedTimestamp => {
412 if maybe_last_updated_timestamp.is_some() {
413 return Err(DeError::duplicate_field(LAST_UPDATED_TIMESTAMP));
414 }
415 maybe_last_updated_timestamp = Some(map.next_value()?);
416 },
417 Field::LastDirtyTimestamp => {
418 if maybe_last_dirty_timestamp.is_some() {
419 return Err(DeError::duplicate_field(LAST_DIRTY_TIMESTAMP));
420 }
421 maybe_last_dirty_timestamp = Some(map.next_value()?);
422 },
423 Field::IsCoordinatingDiscoveryServer => {
424 if maybe_is_coordinating_discovery_server.is_some() {
425 return Err(DeError::duplicate_field(IS_COORDINATED_DISCOVERY_SERVER));
426 }
427 maybe_is_coordinating_discovery_server = Some(map.next_value()?);
428 },
429 Field::ActionType => {
430 if maybe_action_type.is_some() {
431 return Err(DeError::duplicate_field(ACTION_TYPE));
432 }
433 maybe_action_type = Some(map.next_value()?);
434 }
435 }
436 }
437
438 let host_name = maybe_host_name.ok_or_else(|| DeError::missing_field(HOST_NAME));
439 let app = maybe_app.ok_or_else(|| DeError::missing_field(APP));
440 let ip_addr = maybe_ip_addr.ok_or_else(|| DeError::missing_field(IP_ADDR));
441 let vip_address = maybe_vip_address.ok_or_else(|| DeError::missing_field(VIP_ADDRESS));
442 let secure_vip_address = maybe_secure_vip_address.ok_or_else(|| DeError::missing_field(SECURE_VIP_ADDRESS));
443 let status = maybe_status.ok_or_else(|| DeError::missing_field(STATUS));
444 let homepage_url = maybe_homepage_url.ok_or_else(|| DeError::missing_field(HOME_PAGE_URL));
445 let status_page_url = maybe_status_page_url.ok_or_else(|| DeError::missing_field(STATUS_PAGE_URL));
446 let health_check_url = maybe_health_check_url.ok_or_else(|| DeError::missing_field(HEALTH_CHECK_URL));
447 let data_center_info = maybe_data_center_info.ok_or_else(|| DeError::missing_field(DATA_CENTER_INFO));
448 let metadata = maybe_metadata
449 .map(|mut m| {
450 m.remove("@class");
451 m
452 })
453 .unwrap_or(Map::new());
454 let last_updated_timestamp = maybe_last_updated_timestamp.ok_or_else(|| DeError::missing_field(LAST_UPDATED_TIMESTAMP));;
455 let last_dirty_timestamp = maybe_last_dirty_timestamp.ok_or_else(|| DeError::missing_field(LAST_DIRTY_TIMESTAMP));;
456 let is_coordinating_discovery_server = maybe_is_coordinating_discovery_server.ok_or_else(|| DeError::missing_field(IS_COORDINATED_DISCOVERY_SERVER));;
457 let action_type = maybe_action_type.ok_or_else(|| DeError::missing_field(ACTION_TYPE));
458 let country_id = maybe_country_id.ok_or_else(|| DeError::missing_field(COUNTRY_ID));
459
460 Ok(Instance {
461 host_name: host_name?,
462 app: app?,
463 ip_addr: ip_addr?,
464 vip_address: vip_address?,
465 secure_vip_address: secure_vip_address?,
466 status: status?,
467 port: maybe_port.map(|p| p.port),
468 secure_port: maybe_secure_port.map(|p| p.port),
469 homepage_url: homepage_url?,
470 status_page_url: status_page_url?,
471 health_check_url: health_check_url?,
472 data_center_info: data_center_info?,
473 lease_info: maybe_lease_info,
474 metadata: metadata,
475 overriddenstatus: maybe_overriddenstatus,
476 country_id: country_id?,
477 last_updated_timestamp: last_updated_timestamp?,
478 last_dirty_timestamp: last_dirty_timestamp?,
479 is_coordinating_discovery_server: is_coordinating_discovery_server?,
480 action_type: action_type?
481 })
482 }
483 }
484 deserializer.deserialize_struct(INSTANCE, RUST_FIELDS, InstanceVisitor)
485 }
486}
487
488#[cfg(test)]
489pub mod tests {
490 use super::*;
491 use serde_json;
492 use super::super::DcName;
493 use super::super::AmazonMetaData;
494
495 #[test]
496 fn test_instance_serialization() {
497 let json = build_test_instance_json();
498 let instance = build_test_instance();
499 let result = serde_json::to_string(&instance).unwrap();
500
501 assert_eq!(json, result);
507 }
508
509 #[test]
510 fn test_instance_deserialization() {
511 let json = build_test_instance_json();
512 let instance = build_test_instance();
513 let result = serde_json::from_str(&json).unwrap();
514 assert_eq!(instance, result);
515 }
516
517 pub fn build_test_instance_json() -> String {
518 r#"{
519 "hostName": "Foo",
520 "app": "Bar",
521 "ipAddr": "3.128.2.12",
522 "vipAddress": "127.0.0.1",
523 "secureVipAddress": "127.0.0.2",
524 "status": "UP",
525 "port": { "$": "80", "@enabled": "true" },
526 "securePort": { "$": "443", "@enabled": "true" },
527 "homePageUrl": "http://google.com",
528 "statusPageUrl": "http://nytimes.com",
529 "healthCheckUrl": "http://washingtonpost.com",
530 "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name":"Amazon","metadata":
531 {
532 "ami-launch-index": "001a",
533 "local-hostname": "localhost0",
534 "availability-zone": "US_East1a",
535 "instance-id": "instance1a",
536 "public-ipv4": "32.23.21.212",
537 "public-hostname": "foo.coma",
538 "ami-manifest-path": "/dev/nulla",
539 "local-ipv4": "127.0.0.12",
540 "hostname": "privatefoo.coma",
541 "ami-id": "ami0023",
542 "instance-type": "c4xlarged"
543 }},
544 "leaseInfo": {
545 "renewalIntervalInSecs": 30,
546 "durationInSecs": 90,
547 "registrationTimestamp": 1503442035871,
548 "lastRenewalTimestamp": 1503442035871,
549 "evictionTimestamp": 0,
550 "serviceUpTimestamp": 1503442035721
551 },
552 "metadata": {"something": "somethingelse"},
553 "countryId": 1,
554 "overriddenstatus": "UNKNOWN",
555 "isCoordinatingDiscoveryServer": false,
556 "lastUpdatedTimestamp": 1503442035871,
557 "lastDirtyTimestamp": 1503442035714,
558 "actionType": "ADDED"
559 }"#
560 .to_string()
561 .replace(" ", "")
562 .replace("\n", "")
563 }
564
565 pub fn build_test_instance() -> Instance {
566 let mut metadata = Map::new();
567 metadata.insert("something".to_owned(), Value::String("somethingelse".to_owned()));
568 Instance {
569 host_name: "Foo".to_string(),
570 app: "Bar".to_string(),
571 ip_addr: "3.128.2.12".to_string(),
572 vip_address: "127.0.0.1".to_string(),
573 secure_vip_address: "127.0.0.2".to_string(),
574 status: Status::Up,
575 port: Some(80),
576 secure_port: Some(443),
577 homepage_url: "http://google.com".to_string(),
578 status_page_url: "http://nytimes.com".to_string(),
579 health_check_url: "http://washingtonpost.com".to_string(),
580 data_center_info: DataCenterInfo {
581 name: DcName::Amazon,
582 metadata: Some(AmazonMetaData {
583 ami_launch_index: "001a".to_string(),
584 local_hostname: "localhost0".to_string(),
585 availability_zone: "US_East1a".to_string(),
586 instance_id: "instance1a".to_string(),
587 public_ipv4: "32.23.21.212".to_string(),
588 public_hostname: "foo.coma".to_string(),
589 ami_manifest_path: "/dev/nulla".to_string(),
590 local_ipv4: "127.0.0.12".to_string(),
591 hostname: "privatefoo.coma".to_string(),
592 ami_id: "ami0023".to_string(),
593 instance_type: "c4xlarged".to_string()
594 })
595 },
596 lease_info: Some(LeaseInfo {
597 renewal_interval_in_secs: 30,
598 duration_in_secs: 90,
599 registration_timestamp: 1503442035871,
600 last_renewal_timestamp: 1503442035871,
601 eviction_timestamp: 0,
602 service_up_timestamp: 1503442035721,
603 }),
604 metadata: metadata,
605 overriddenstatus: Some(Status::Unknown),
606 country_id: 1,
607 last_dirty_timestamp: 1503442035714,
608 last_updated_timestamp: 1503442035871,
609 action_type: ActionType::Added,
610 is_coordinating_discovery_server: false
611 }
612 }
613
614 #[test]
615 fn test_empty_map() {
616 let json = r#"{
617 "hostName": "Foo",
618 "app": "Bar",
619 "ipAddr": "3.128.2.12",
620 "vipAddress": "127.0.0.1",
621 "secureVipAddress": "127.0.0.2",
622 "status": "UP",
623 "port": { "$": "80", "@enabled": "true" },
624 "securePort": { "$": "443", "@enabled": "true" },
625 "homePageUrl": "http://google.com",
626 "statusPageUrl": "http://nytimes.com",
627 "healthCheckUrl": "http://washingtonpost.com",
628 "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name":"Amazon","metadata":
629 {
630 "ami-launch-index": "001a",
631 "local-hostname": "localhost0",
632 "availability-zone": "US_East1a",
633 "instance-id": "instance1a",
634 "public-ipv4": "32.23.21.212",
635 "public-hostname": "foo.coma",
636 "ami-manifest-path": "/dev/nulla",
637 "local-ipv4": "127.0.0.12",
638 "hostname": "privatefoo.coma",
639 "ami-id": "ami0023",
640 "instance-type": "c4xlarged"
641 }},
642 "leaseInfo": { "renewalIntervalInSecs": 30,
643 "durationInSecs": 90,
644 "registrationTimestamp": 1503442035871,
645 "lastRenewalTimestamp": 1503442035871,
646 "evictionTimestamp": 0,
647 "serviceUpTimestamp": 1503442035721
648 },
649 "metadata": {"@class": "java.util.Collections$EmptyMap"},
650 "countryId": 1,
651 "overriddenstatus": "UNKNOWN",
652 "isCoordinatingDiscoveryServer": false,
653 "lastUpdatedTimestamp": 1503442035871,
654 "lastDirtyTimestamp": 1503442035714,
655 "actionType": "ADDED"
656 }"#;
657
658 let instance: Instance = serde_json::from_str(json).unwrap();
659 assert_eq!(0, instance.metadata.len());
660 }
661}
662