Skip to main content

rust_eureka/request/
instance.rs

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