1use super::Application;
2use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
3use serde::ser::{Serialize, SerializeStruct, Serializer};
4use std::fmt;
5
6#[derive(Debug, PartialEq)]
7pub struct Applications {
8 pub versions_delta: i16,
9 pub apps_hashcode: String,
10 pub applications: Vec<Application>,
11}
12
13fn deserialize_application_field<'de, D>(deserializer: D) -> Result<Vec<Application>, D::Error>
15where
16 D: Deserializer<'de>,
17{
18 struct ApplicationOrVec;
19
20 impl<'de> Visitor<'de> for ApplicationOrVec {
21 type Value = Vec<Application>;
22
23 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
24 formatter.write_str("application or array of applications")
25 }
26
27 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
28 where
29 A: MapAccess<'de>,
30 {
31 let app: Application =
32 Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
33 Ok(vec![app])
34 }
35
36 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
37 where
38 A: SeqAccess<'de>,
39 {
40 Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
41 }
42 }
43
44 deserializer.deserialize_any(ApplicationOrVec)
45}
46
47impl Serialize for Applications {
49 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50 where
51 S: Serializer,
52 {
53 let mut state = serializer.serialize_struct("Applications", 3)?;
54 state.serialize_field("versions__delta", &self.versions_delta)?;
55 state.serialize_field("apps__hashcode", &self.apps_hashcode)?;
56 if self.applications.len() == 1 {
58 state.serialize_field("application", &self.applications[0])?;
59 } else {
60 state.serialize_field("application", &self.applications)?;
61 }
62 state.end()
63 }
64}
65
66impl<'de> Deserialize<'de> for Applications {
68 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
69 where
70 D: Deserializer<'de>,
71 {
72 struct ApplicationsVisitor;
73
74 impl<'de> Visitor<'de> for ApplicationsVisitor {
75 type Value = Applications;
76
77 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
78 formatter.write_str("struct Applications")
79 }
80
81 fn visit_map<V>(self, mut map: V) -> Result<Applications, V::Error>
82 where
83 V: MapAccess<'de>,
84 {
85 let mut versions_delta = None;
86 let mut apps_hashcode = None;
87 let mut applications = None;
88
89 while let Some(key) = map.next_key::<String>()? {
90 match key.as_str() {
91 "versions__delta" => {
92 if versions_delta.is_some() {
93 return Err(de::Error::duplicate_field("versions__delta"));
94 }
95 let val: serde_json::Value = map.next_value()?;
97 let parsed = match val {
98 serde_json::Value::Number(n) => n.as_i64().map(|i| i as i16),
99 serde_json::Value::String(s) => s.parse::<i16>().ok(),
100 _ => None,
101 };
102 if let Some(v) = parsed {
103 versions_delta = Some(v);
104 } else {
105 return Err(de::Error::custom("Invalid versions__delta type"));
106 }
107 }
108 "apps__hashcode" => {
109 if apps_hashcode.is_some() {
110 return Err(de::Error::duplicate_field("apps__hashcode"));
111 }
112 let val: serde_json::Value = map.next_value()?;
114 let s = match val {
115 serde_json::Value::String(s) => s,
116 serde_json::Value::Number(n) => n.to_string(),
117 _ => return Err(de::Error::custom("Invalid apps__hashcode type")),
118 };
119 apps_hashcode = Some(s);
120 }
121 "application" => {
122 if applications.is_some() {
123 return Err(de::Error::duplicate_field("application"));
124 }
125 applications = Some(map.next_value_seed(ApplicationFieldSeed)?);
126 }
127 _ => {
128 let _: de::IgnoredAny = map.next_value()?;
130 }
131 }
132 }
133
134 let versions_delta =
135 versions_delta.ok_or_else(|| de::Error::missing_field("versions__delta"))?;
136 let apps_hashcode =
137 apps_hashcode.ok_or_else(|| de::Error::missing_field("apps__hashcode"))?;
138 let applications =
139 applications.ok_or_else(|| de::Error::missing_field("application"))?;
140
141 Ok(Applications {
142 versions_delta,
143 apps_hashcode,
144 applications,
145 })
146 }
147 }
148
149 struct ApplicationFieldSeed;
150
151 impl<'de> de::DeserializeSeed<'de> for ApplicationFieldSeed {
152 type Value = Vec<Application>;
153
154 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
155 where
156 D: Deserializer<'de>,
157 {
158 deserialize_application_field(deserializer)
159 }
160 }
161
162 const FIELDS: &[&str] = &["versions__delta", "apps__hashcode", "application"];
163 deserializer.deserialize_struct("Applications", FIELDS, ApplicationsVisitor)
164 }
165}
166
167impl From<Application> for Vec<Application> {
168 fn from(v: Application) -> Self {
169 vec![v]
170 }
171}
172
173#[cfg(test)]
174pub mod tests {
175 use super::super::ActionType;
176 use super::super::Application;
177 use super::super::DataCenterInfo;
178 use super::super::DcName;
179 use super::super::Instance;
180 use super::super::LeaseInfo;
181 use super::super::Status;
182 use super::*;
183 use serde_json::{self, Map};
184
185 #[test]
186 fn test_applications_serialize() {
187 let applications = build_test_applications();
188 let result = serde_json::to_string(&applications).unwrap();
189 assert!(result.contains("\"apps__hashcode\":\"UP_1_\""));
190 assert!(result.contains("\"name\":\"INTEGRATION_TEST\""));
191 }
192
193 #[test]
194 fn test_applications_deserialize() {
195 let json = build_test_applications_json();
196 let applications = build_test_applications();
197 let result = serde_json::from_str(json.as_ref()).unwrap();
198
199 assert_eq!(applications, result)
200 }
201
202 #[test]
203 fn test_applications_multi_deserialize() {
204 let json = build_test_multi_applications_json();
205 let result: Applications = serde_json::from_str(json.as_ref()).unwrap();
206 assert_eq!(2, result.applications.len())
207 }
208
209 pub fn build_test_applications() -> Applications {
210 Applications {
211 versions_delta: 1,
212 apps_hashcode: "UP_1_".to_string(),
213 applications: vec![Application {
214 name: "INTEGRATION_TEST".to_string(),
215 instance: vec![Instance {
216 host_name: "localhost".to_string(),
217 app: "INTEGRATION_TEST".to_string(),
218 ip_addr: "127.0.0.1".to_string(),
219 status: Status::Up,
220 overriddenstatus: Some(Status::Unknown),
221 port: Some(7001),
222 secure_port: Some(7002),
223 country_id: 1,
224 data_center_info: DataCenterInfo {
225 name: DcName::MyOwn,
226 metadata: None,
227 },
228 lease_info: Some(LeaseInfo {
229 renewal_interval_in_secs: 30,
230 duration_in_secs: 90,
231 registration_timestamp: 1503701416749,
232 last_renewal_timestamp: 1503701416749,
233 eviction_timestamp: 0,
234 service_up_timestamp: 1503701416464,
235 }),
236 metadata: Map::new(),
237 homepage_url: "http://google.com".to_string(),
238 status_page_url: "http://google.com".to_string(),
239 health_check_url: "http://google.com".to_string(),
240 vip_address: "127.0.0.1".to_string(),
241 secure_vip_address: "127.0.0.1".to_string(),
242 is_coordinating_discovery_server: false,
243 last_updated_timestamp: 1503701416750,
244 last_dirty_timestamp: 1503701416457,
245 action_type: ActionType::Added,
246 }],
247 }],
248 }
249 }
250
251 pub fn build_test_applications_json() -> String {
252 r#"
253 {
254 "versions__delta": 1,
255 "apps__hashcode": "UP_1_",
256 "application": {
257 "name": "INTEGRATION_TEST",
258 "instance": [{
259 "hostName": "localhost",
260 "app": "INTEGRATION_TEST",
261 "ipAddr": "127.0.0.1",
262 "status": "UP",
263 "overriddenstatus": "UNKNOWN",
264 "port": {
265 "@enabled": "true",
266 "$": "7001"
267 },
268 "securePort": {
269 "@enabled": "false",
270 "$": "7002"
271 },
272 "countryId": 1,
273 "dataCenterInfo": {
274 "name": "MyOwn"
275 },
276 "leaseInfo": {
277 "renewalIntervalInSecs": 30,
278 "durationInSecs": 90,
279 "registrationTimestamp": 1503701416749,
280 "lastRenewalTimestamp": 1503701416749,
281 "evictionTimestamp": 0,
282 "serviceUpTimestamp": 1503701416464
283 },
284 "metadata": {
285 "@class": "java.util.Collections$EmptyMap"
286 },
287 "homePageUrl": "http://google.com",
288 "statusPageUrl": "http://google.com",
289 "healthCheckUrl": "http://google.com",
290 "vipAddress": "127.0.0.1",
291 "secureVipAddress": "127.0.0.1",
292 "isCoordinatingDiscoveryServer": false,
293 "lastUpdatedTimestamp": 1503701416750,
294 "lastDirtyTimestamp": 1503701416457,
295 "actionType": "ADDED"
296 }]
297 }
298 }
299 "#
300 .to_string()
301 .replace(" ", "")
302 .replace("\n", "")
303 }
304
305 pub fn build_test_multi_applications_json() -> String {
306 r#"
307 {
308 "versions__delta": 1,
309 "apps__hashcode": "UP_1_",
310 "application": [{
311 "name": "INTEGRATION_TEST",
312 "instance": [{
313 "hostName": "localhost",
314 "app": "INTEGRATION_TEST",
315 "ipAddr": "127.0.0.1",
316 "status": "UP",
317 "overriddenstatus": "UNKNOWN",
318 "port": {
319 "@enabled": "true",
320 "$": "7001"
321 },
322 "securePort": {
323 "@enabled": "false",
324 "$": "7002"
325 },
326 "countryId": 1,
327 "dataCenterInfo": {
328 "name": "MyOwn"
329 },
330 "leaseInfo": {
331 "renewalIntervalInSecs": 30,
332 "durationInSecs": 90,
333 "registrationTimestamp": 1503701416749,
334 "lastRenewalTimestamp": 1503701416749,
335 "evictionTimestamp": 0,
336 "serviceUpTimestamp": 1503701416464
337 },
338 "metadata": {
339 "@class": "java.util.Collections$EmptyMap"
340 },
341 "homePageUrl": "http://google.com",
342 "statusPageUrl": "http://google.com",
343 "healthCheckUrl": "http://google.com",
344 "vipAddress": "127.0.0.1",
345 "secureVipAddress": "127.0.0.1",
346 "isCoordinatingDiscoveryServer": false,
347 "lastUpdatedTimestamp": 1503701416750,
348 "lastDirtyTimestamp": 1503701416457,
349 "actionType": "ADDED"
350 }]
351 }, {
352 "name": "INTEGRATION_TEST2",
353 "instance": [{
354 "hostName": "localhost",
355 "app": "INTEGRATION_TEST",
356 "ipAddr": "127.0.0.1",
357 "status": "UP",
358 "overriddenstatus": "UNKNOWN",
359 "port": {
360 "@enabled": "true",
361 "$": "7001"
362 },
363 "securePort": {
364 "@enabled": "false",
365 "$": "7002"
366 },
367 "countryId": 1,
368 "dataCenterInfo": {
369 "name": "MyOwn"
370 },
371 "leaseInfo": {
372 "renewalIntervalInSecs": 30,
373 "durationInSecs": 90,
374 "registrationTimestamp": 1503701416749,
375 "lastRenewalTimestamp": 1503701416749,
376 "evictionTimestamp": 0,
377 "serviceUpTimestamp": 1503701416464
378 },
379 "metadata": {
380 "@class": "java.util.Collections$EmptyMap"
381 },
382 "homePageUrl": "http://google.com",
383 "statusPageUrl": "http://google.com",
384 "healthCheckUrl": "http://google.com",
385 "vipAddress": "127.0.0.1",
386 "secureVipAddress": "127.0.0.1",
387 "isCoordinatingDiscoveryServer": false,
388 "lastUpdatedTimestamp": 1503701416750,
389 "lastDirtyTimestamp": 1503701416457,
390 "actionType": "ADDED"
391 }]
392 }]
393 }
394 "#
395 .to_string()
396 .replace(" ", "")
397 .replace("\n", "")
398 }
399}