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
10const 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 s.serialize_field(PORT_DOLLAR, &self.port.to_string())?;
106 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 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 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 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 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 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}