Skip to main content

rust_eureka/response/
instance.rs

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