1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct FirewallPolicySource {
11 pub zone_id: Option<Uuid>,
12 #[serde(default)]
13 pub traffic_filter: Option<SourceTrafficFilter>,
14}
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct FirewallPolicyDestination {
20 pub zone_id: Option<Uuid>,
21 #[serde(default)]
22 pub traffic_filter: Option<DestTrafficFilter>,
23}
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26#[serde(tag = "type")]
27pub enum SourceTrafficFilter {
28 #[serde(rename = "NETWORK")]
29 Network {
30 #[serde(rename = "networkFilter")]
31 network_filter: NetworkFilter,
32 #[serde(
33 rename = "macAddressFilter",
34 default,
35 skip_serializing_if = "Option::is_none"
36 )]
37 mac_address_filter: Option<MacAddressFilter>,
38 #[serde(
39 rename = "portFilter",
40 default,
41 skip_serializing_if = "Option::is_none"
42 )]
43 port_filter: Option<PortFilter>,
44 },
45 #[serde(rename = "IP_ADDRESS")]
46 IpAddress {
47 #[serde(rename = "ipAddressFilter")]
48 ip_address_filter: IpAddressFilter,
49 #[serde(
50 rename = "macAddressFilter",
51 default,
52 skip_serializing_if = "Option::is_none"
53 )]
54 mac_address_filter: Option<MacAddressFilter>,
55 #[serde(
56 rename = "portFilter",
57 default,
58 skip_serializing_if = "Option::is_none"
59 )]
60 port_filter: Option<PortFilter>,
61 },
62 #[serde(rename = "MAC_ADDRESS")]
63 MacAddress {
64 #[serde(rename = "macAddressFilter")]
65 mac_address_filter: MacAddressFilter,
66 #[serde(
67 rename = "portFilter",
68 default,
69 skip_serializing_if = "Option::is_none"
70 )]
71 port_filter: Option<PortFilter>,
72 },
73 #[serde(rename = "PORT")]
74 Port {
75 #[serde(rename = "portFilter")]
76 port_filter: PortFilter,
77 },
78 #[serde(rename = "REGION")]
79 Region {
80 #[serde(rename = "regionFilter")]
81 region_filter: RegionFilter,
82 #[serde(
83 rename = "portFilter",
84 default,
85 skip_serializing_if = "Option::is_none"
86 )]
87 port_filter: Option<PortFilter>,
88 },
89 #[serde(other)]
90 Unknown,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94#[serde(tag = "type")]
95pub enum DestTrafficFilter {
96 #[serde(rename = "NETWORK")]
97 Network {
98 #[serde(rename = "networkFilter")]
99 network_filter: NetworkFilter,
100 #[serde(
101 rename = "portFilter",
102 default,
103 skip_serializing_if = "Option::is_none"
104 )]
105 port_filter: Option<PortFilter>,
106 },
107 #[serde(rename = "IP_ADDRESS")]
108 IpAddress {
109 #[serde(rename = "ipAddressFilter")]
110 ip_address_filter: IpAddressFilter,
111 #[serde(
112 rename = "portFilter",
113 default,
114 skip_serializing_if = "Option::is_none"
115 )]
116 port_filter: Option<PortFilter>,
117 },
118 #[serde(rename = "PORT")]
119 Port {
120 #[serde(rename = "portFilter")]
121 port_filter: PortFilter,
122 },
123 #[serde(rename = "REGION")]
124 Region {
125 #[serde(rename = "regionFilter")]
126 region_filter: RegionFilter,
127 #[serde(
128 rename = "portFilter",
129 default,
130 skip_serializing_if = "Option::is_none"
131 )]
132 port_filter: Option<PortFilter>,
133 },
134 #[serde(rename = "APPLICATION")]
135 Application {
136 #[serde(rename = "applicationFilter")]
137 application_filter: ApplicationFilter,
138 #[serde(
139 rename = "portFilter",
140 default,
141 skip_serializing_if = "Option::is_none"
142 )]
143 port_filter: Option<PortFilter>,
144 },
145 #[serde(rename = "APPLICATION_CATEGORY")]
146 ApplicationCategory {
147 #[serde(rename = "applicationCategoryFilter")]
148 application_category_filter: ApplicationCategoryFilter,
149 #[serde(
150 rename = "portFilter",
151 default,
152 skip_serializing_if = "Option::is_none"
153 )]
154 port_filter: Option<PortFilter>,
155 },
156 #[serde(rename = "DOMAIN")]
157 Domain {
158 #[serde(rename = "domainFilter")]
159 domain_filter: DomainFilter,
160 #[serde(
161 rename = "portFilter",
162 default,
163 skip_serializing_if = "Option::is_none"
164 )]
165 port_filter: Option<PortFilter>,
166 },
167 #[serde(other)]
168 Unknown,
169}
170
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct NetworkFilter {
174 pub network_ids: Vec<Uuid>,
175 #[serde(default)]
176 pub match_opposite: bool,
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
180#[serde(tag = "type")]
181pub enum IpAddressFilter {
182 #[serde(rename = "IP_ADDRESSES", alias = "SPECIFIC")]
183 Specific {
184 #[serde(default)]
185 items: Vec<IpAddressItem>,
186 #[serde(default, rename = "matchOpposite")]
187 match_opposite: bool,
188 },
189 #[serde(rename = "TRAFFIC_MATCHING_LIST")]
190 TrafficMatchingList {
191 #[serde(rename = "trafficMatchingListId")]
192 traffic_matching_list_id: Uuid,
193 #[serde(default, rename = "matchOpposite")]
194 match_opposite: bool,
195 },
196 #[serde(other)]
197 Unknown,
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201#[serde(tag = "type")]
202pub enum IpAddressItem {
203 #[serde(rename = "IP_ADDRESS")]
204 Address { value: String },
205 #[serde(rename = "RANGE")]
206 Range { start: String, stop: String },
207 #[serde(rename = "SUBNET")]
208 Subnet { value: String },
209}
210
211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
212#[serde(tag = "type")]
213pub enum PortFilter {
214 #[serde(rename = "PORTS", alias = "VALUE")]
215 Ports {
216 #[serde(default)]
217 items: Vec<PortItem>,
218 #[serde(default, rename = "matchOpposite")]
219 match_opposite: bool,
220 },
221 #[serde(rename = "TRAFFIC_MATCHING_LIST")]
222 TrafficMatchingList {
223 #[serde(rename = "trafficMatchingListId")]
224 traffic_matching_list_id: Uuid,
225 #[serde(default, rename = "matchOpposite")]
226 match_opposite: bool,
227 },
228 #[serde(other)]
229 Unknown,
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233#[serde(tag = "type")]
234pub enum PortItem {
235 #[serde(rename = "PORT_NUMBER")]
236 Number {
237 #[serde(deserialize_with = "deserialize_port_value")]
238 value: String,
239 },
240 #[serde(rename = "PORT_NUMBER_RANGE", alias = "PORT_RANGE")]
241 Range {
242 #[serde(rename = "startPort", deserialize_with = "deserialize_port_value")]
243 start_port: String,
244 #[serde(rename = "endPort", deserialize_with = "deserialize_port_value")]
245 end_port: String,
246 },
247 #[serde(other)]
248 Unknown,
249}
250
251fn deserialize_port_value<'de, D>(deserializer: D) -> Result<String, D::Error>
252where
253 D: serde::Deserializer<'de>,
254{
255 struct PortValueVisitor;
256
257 impl serde::de::Visitor<'_> for PortValueVisitor {
258 type Value = String;
259
260 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
261 formatter.write_str("a port number as string or integer")
262 }
263
264 fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<String, E> {
265 Ok(value.to_string())
266 }
267
268 fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<String, E> {
269 Ok(value.to_string())
270 }
271
272 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<String, E> {
273 Ok(value.to_string())
274 }
275 }
276
277 deserializer.deserialize_any(PortValueVisitor)
278}
279
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
281#[serde(rename_all = "camelCase")]
282pub struct MacAddressFilter {
283 pub mac_addresses: Vec<String>,
284}
285
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct ApplicationFilter {
289 pub application_ids: Vec<i64>,
290}
291
292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293#[serde(rename_all = "camelCase")]
294pub struct ApplicationCategoryFilter {
295 pub application_category_ids: Vec<i64>,
296}
297
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299pub struct RegionFilter {
300 pub regions: Vec<String>,
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
304#[serde(tag = "type")]
305pub enum DomainFilter {
306 #[serde(rename = "SPECIFIC")]
307 Specific { domains: Vec<String> },
308 #[serde(other)]
309 Unknown,
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
313#[serde(rename_all = "camelCase")]
314pub struct FirewallPolicyResponse {
315 #[serde(default)]
318 pub id: Option<Uuid>,
319 pub name: String,
320 #[serde(default)]
321 pub description: Option<String>,
322 pub enabled: bool,
323 pub action: Value,
324 pub ip_protocol_scope: Option<Value>,
325 #[serde(default)]
326 pub logging_enabled: bool,
327 pub metadata: Option<Value>,
328 #[serde(default)]
329 pub source: Option<FirewallPolicySource>,
330 #[serde(default)]
331 pub destination: Option<FirewallPolicyDestination>,
332 #[serde(flatten)]
333 pub extra: HashMap<String, Value>,
334}
335
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337#[serde(rename_all = "camelCase")]
338pub struct FirewallPolicyCreateUpdate {
339 pub name: String,
340 pub description: Option<String>,
341 pub enabled: bool,
342 pub action: Value,
343 pub source: Value,
344 pub destination: Value,
345 pub ip_protocol_scope: Value,
346 pub logging_enabled: bool,
347 pub ipsec_filter: Option<String>,
348 pub schedule: Option<Value>,
349 pub connection_state_filter: Option<Vec<String>>,
350}
351
352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub struct FirewallPolicyPatch {
355 #[serde(skip_serializing_if = "Option::is_none")]
356 pub enabled: Option<bool>,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub logging_enabled: Option<bool>,
359}
360
361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
362#[serde(rename_all = "camelCase")]
363pub struct FirewallPolicyOrderingEnvelope {
364 pub ordered_firewall_policy_ids: FirewallPolicyOrdering,
365}
366
367#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub struct FirewallPolicyOrdering {
370 pub before_system_defined: Vec<Uuid>,
371 pub after_system_defined: Vec<Uuid>,
372}
373
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
375#[serde(rename_all = "camelCase")]
376pub struct FirewallZoneResponse {
377 pub id: Uuid,
378 pub name: String,
379 pub network_ids: Vec<Uuid>,
380 pub metadata: Value,
381}
382
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct FirewallZoneCreateUpdate {
386 pub name: String,
387 pub network_ids: Vec<Uuid>,
388}
389
390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
391#[serde(rename_all = "camelCase")]
392pub struct AclRuleResponse {
393 pub id: Uuid,
394 pub name: String,
395 #[serde(rename = "type")]
396 pub rule_type: String,
397 pub action: String,
398 pub enabled: bool,
399 pub index: i32,
400 pub description: Option<String>,
401 pub source_filter: Option<Value>,
402 pub destination_filter: Option<Value>,
403 pub enforcing_device_filter: Option<Value>,
404 pub metadata: Value,
405}
406
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408#[serde(rename_all = "camelCase")]
409pub struct AclRuleCreateUpdate {
410 pub name: String,
411 #[serde(rename = "type")]
412 pub rule_type: String,
413 pub action: String,
414 pub enabled: bool,
415 pub description: Option<String>,
416 pub source_filter: Option<Value>,
417 pub destination_filter: Option<Value>,
418 pub enforcing_device_filter: Option<Value>,
419}
420
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
422#[serde(rename_all = "camelCase")]
423pub struct AclRuleOrdering {
424 pub ordered_acl_rule_ids: Vec<Uuid>,
425}
426
427#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase")]
429pub struct DnsPolicyResponse {
430 pub id: Uuid,
431 #[serde(rename = "type")]
432 pub policy_type: String,
433 pub enabled: bool,
434 pub domain: Option<String>,
435 pub metadata: Value,
436 #[serde(flatten)]
437 pub extra: HashMap<String, Value>,
438}
439
440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct DnsPolicyCreateUpdate {
443 #[serde(rename = "type")]
444 pub policy_type: String,
445 pub enabled: bool,
446 #[serde(flatten)]
447 pub fields: serde_json::Map<String, Value>,
448}
449
450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct TrafficMatchingListResponse {
453 pub id: Uuid,
454 pub name: String,
455 #[serde(rename = "type")]
456 pub list_type: String,
457 #[serde(flatten)]
458 pub extra: HashMap<String, Value>,
459}
460
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct TrafficMatchingListCreateUpdate {
464 pub name: String,
465 #[serde(rename = "type")]
466 pub list_type: String,
467 #[serde(flatten)]
468 pub fields: serde_json::Map<String, Value>,
469}
470
471#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub struct VoucherResponse {
474 pub id: Uuid,
475 pub code: String,
476 pub name: String,
477 pub created_at: String,
478 pub activated_at: Option<String>,
479 pub expires_at: Option<String>,
480 pub expired: bool,
481 pub time_limit_minutes: i64,
482 pub authorized_guest_count: i64,
483 pub authorized_guest_limit: Option<i64>,
484 pub data_usage_limit_m_bytes: Option<i64>,
485 pub rx_rate_limit_kbps: Option<i64>,
486 pub tx_rate_limit_kbps: Option<i64>,
487}
488
489#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
490#[serde(rename_all = "camelCase")]
491pub struct VoucherCreateRequest {
492 pub name: String,
493 pub count: Option<i32>,
494 pub time_limit_minutes: i64,
495 pub authorized_guest_limit: Option<i64>,
496 pub data_usage_limit_m_bytes: Option<i64>,
497 pub rx_rate_limit_kbps: Option<i64>,
498 pub tx_rate_limit_kbps: Option<i64>,
499}
500
501#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
502#[serde(rename_all = "camelCase")]
503pub struct VoucherDeletionResults {
504 #[serde(flatten)]
505 pub fields: HashMap<String, Value>,
506}
507
508#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
511#[serde(rename_all = "camelCase")]
512pub struct NatPolicyResponse {
513 pub id: Uuid,
514 pub name: String,
515 #[serde(default)]
516 pub description: Option<String>,
517 pub enabled: bool,
518 #[serde(rename = "type")]
519 pub nat_type: String,
520 #[serde(default)]
521 pub interface_id: Option<Uuid>,
522 #[serde(default)]
523 pub protocol: Option<String>,
524 #[serde(default)]
525 pub source: Option<Value>,
526 #[serde(default)]
527 pub destination: Option<Value>,
528 #[serde(default)]
529 pub translated_address: Option<String>,
530 #[serde(default)]
531 pub translated_port: Option<String>,
532 pub metadata: Option<Value>,
533 #[serde(flatten)]
534 pub extra: HashMap<String, Value>,
535}
536
537#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase")]
539pub struct NatPolicyCreateUpdate {
540 pub name: String,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 pub description: Option<String>,
543 pub enabled: bool,
544 #[serde(rename = "type")]
545 pub nat_type: String,
546 #[serde(skip_serializing_if = "Option::is_none")]
547 pub interface_id: Option<Uuid>,
548 #[serde(skip_serializing_if = "Option::is_none")]
549 pub protocol: Option<String>,
550 #[serde(skip_serializing_if = "Option::is_none")]
551 pub source: Option<Value>,
552 #[serde(skip_serializing_if = "Option::is_none")]
553 pub destination: Option<Value>,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 pub translated_address: Option<String>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 pub translated_port: Option<String>,
558}
559
560#[cfg(test)]
561#[allow(clippy::unwrap_used)]
562mod tests {
563 use super::{IpAddressFilter, PortFilter, PortItem};
564
565 #[test]
566 fn port_filter_accepts_value_alias_and_numeric_ports() {
567 let filter: PortFilter = serde_json::from_value(serde_json::json!({
568 "type": "VALUE",
569 "items": [
570 { "type": "PORT_NUMBER", "value": 443 },
571 { "type": "PORT_RANGE", "startPort": 8000, "endPort": "9000" }
572 ],
573 "matchOpposite": true
574 }))
575 .unwrap();
576
577 match filter {
578 PortFilter::Ports {
579 items,
580 match_opposite,
581 } => {
582 assert!(match_opposite);
583 assert_eq!(
584 items,
585 vec![
586 PortItem::Number {
587 value: "443".into()
588 },
589 PortItem::Range {
590 start_port: "8000".into(),
591 end_port: "9000".into()
592 },
593 ]
594 );
595 }
596 other => panic!("unexpected filter: {other:?}"),
597 }
598 }
599
600 #[test]
601 fn ip_address_filter_accepts_specific_alias() {
602 let filter: IpAddressFilter = serde_json::from_value(serde_json::json!({
603 "type": "SPECIFIC",
604 "items": [{ "type": "IP_ADDRESS", "value": "192.168.1.10" }],
605 "matchOpposite": false
606 }))
607 .unwrap();
608
609 assert!(matches!(filter, IpAddressFilter::Specific { .. }));
610 }
611
612 #[test]
613 fn port_item_range_serializes_as_port_number_range() {
614 let item = PortItem::Range {
615 start_port: "49152".into(),
616 end_port: "65535".into(),
617 };
618 let json = serde_json::to_value(&item).unwrap();
619 assert_eq!(
620 json.get("type").and_then(serde_json::Value::as_str),
621 Some("PORT_NUMBER_RANGE"),
622 "Range must serialize as PORT_NUMBER_RANGE, not PORT_RANGE"
623 );
624 assert_eq!(
625 json.get("startPort").and_then(serde_json::Value::as_str),
626 Some("49152")
627 );
628 assert_eq!(
629 json.get("endPort").and_then(serde_json::Value::as_str),
630 Some("65535")
631 );
632 }
633
634 #[test]
635 fn port_item_range_deserializes_from_port_range_alias() {
636 let item: PortItem = serde_json::from_value(serde_json::json!({
637 "type": "PORT_RANGE",
638 "startPort": "8000",
639 "endPort": "9000"
640 }))
641 .unwrap();
642 assert!(
643 matches!(item, PortItem::Range { .. }),
644 "PORT_RANGE alias must still deserialize"
645 );
646 }
647}