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_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 pub id: Uuid,
316 pub name: String,
317 #[serde(default)]
318 pub description: Option<String>,
319 pub enabled: bool,
320 pub action: Value,
321 pub ip_protocol_scope: Option<Value>,
322 #[serde(default)]
323 pub logging_enabled: bool,
324 pub metadata: Option<Value>,
325 #[serde(default)]
326 pub source: Option<FirewallPolicySource>,
327 #[serde(default)]
328 pub destination: Option<FirewallPolicyDestination>,
329 #[serde(flatten)]
330 pub extra: HashMap<String, Value>,
331}
332
333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct FirewallPolicyCreateUpdate {
336 pub name: String,
337 pub description: Option<String>,
338 pub enabled: bool,
339 pub action: Value,
340 pub source: Value,
341 pub destination: Value,
342 pub ip_protocol_scope: Value,
343 pub logging_enabled: bool,
344 pub ipsec_filter: Option<String>,
345 pub schedule: Option<Value>,
346 pub connection_state_filter: Option<Vec<String>>,
347}
348
349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct FirewallPolicyPatch {
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub enabled: Option<bool>,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub logging_enabled: Option<bool>,
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
359#[serde(rename_all = "camelCase")]
360pub struct FirewallPolicyOrderingEnvelope {
361 pub ordered_firewall_policy_ids: FirewallPolicyOrdering,
362}
363
364#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
365#[serde(rename_all = "camelCase")]
366pub struct FirewallPolicyOrdering {
367 pub before_system_defined: Vec<Uuid>,
368 pub after_system_defined: Vec<Uuid>,
369}
370
371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
372#[serde(rename_all = "camelCase")]
373pub struct FirewallZoneResponse {
374 pub id: Uuid,
375 pub name: String,
376 pub network_ids: Vec<Uuid>,
377 pub metadata: Value,
378}
379
380#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
381#[serde(rename_all = "camelCase")]
382pub struct FirewallZoneCreateUpdate {
383 pub name: String,
384 pub network_ids: Vec<Uuid>,
385}
386
387#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct AclRuleResponse {
390 pub id: Uuid,
391 pub name: String,
392 #[serde(rename = "type")]
393 pub rule_type: String,
394 pub action: String,
395 pub enabled: bool,
396 pub index: i32,
397 pub description: Option<String>,
398 pub source_filter: Option<Value>,
399 pub destination_filter: Option<Value>,
400 pub enforcing_device_filter: Option<Value>,
401 pub metadata: Value,
402}
403
404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405#[serde(rename_all = "camelCase")]
406pub struct AclRuleCreateUpdate {
407 pub name: String,
408 #[serde(rename = "type")]
409 pub rule_type: String,
410 pub action: String,
411 pub enabled: bool,
412 pub description: Option<String>,
413 pub source_filter: Option<Value>,
414 pub destination_filter: Option<Value>,
415 pub enforcing_device_filter: Option<Value>,
416}
417
418#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct AclRuleOrdering {
421 pub ordered_acl_rule_ids: Vec<Uuid>,
422}
423
424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
425#[serde(rename_all = "camelCase")]
426pub struct DnsPolicyResponse {
427 pub id: Uuid,
428 #[serde(rename = "type")]
429 pub policy_type: String,
430 pub enabled: bool,
431 pub domain: Option<String>,
432 pub metadata: Value,
433 #[serde(flatten)]
434 pub extra: HashMap<String, Value>,
435}
436
437#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
438#[serde(rename_all = "camelCase")]
439pub struct DnsPolicyCreateUpdate {
440 #[serde(rename = "type")]
441 pub policy_type: String,
442 pub enabled: bool,
443 #[serde(flatten)]
444 pub fields: serde_json::Map<String, Value>,
445}
446
447#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
448#[serde(rename_all = "camelCase")]
449pub struct TrafficMatchingListResponse {
450 pub id: Uuid,
451 pub name: String,
452 #[serde(rename = "type")]
453 pub list_type: String,
454 #[serde(flatten)]
455 pub extra: HashMap<String, Value>,
456}
457
458#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
459#[serde(rename_all = "camelCase")]
460pub struct TrafficMatchingListCreateUpdate {
461 pub name: String,
462 #[serde(rename = "type")]
463 pub list_type: String,
464 #[serde(flatten)]
465 pub fields: serde_json::Map<String, Value>,
466}
467
468#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
469#[serde(rename_all = "camelCase")]
470pub struct VoucherResponse {
471 pub id: Uuid,
472 pub code: String,
473 pub name: String,
474 pub created_at: String,
475 pub activated_at: Option<String>,
476 pub expires_at: Option<String>,
477 pub expired: bool,
478 pub time_limit_minutes: i64,
479 pub authorized_guest_count: i64,
480 pub authorized_guest_limit: Option<i64>,
481 pub data_usage_limit_m_bytes: Option<i64>,
482 pub rx_rate_limit_kbps: Option<i64>,
483 pub tx_rate_limit_kbps: Option<i64>,
484}
485
486#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
487#[serde(rename_all = "camelCase")]
488pub struct VoucherCreateRequest {
489 pub name: String,
490 pub count: Option<i32>,
491 pub time_limit_minutes: i64,
492 pub authorized_guest_limit: Option<i64>,
493 pub data_usage_limit_m_bytes: Option<i64>,
494 pub rx_rate_limit_kbps: Option<i64>,
495 pub tx_rate_limit_kbps: Option<i64>,
496}
497
498#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
499#[serde(rename_all = "camelCase")]
500pub struct VoucherDeletionResults {
501 #[serde(flatten)]
502 pub fields: HashMap<String, Value>,
503}
504
505#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
508#[serde(rename_all = "camelCase")]
509pub struct NatPolicyResponse {
510 pub id: Uuid,
511 pub name: String,
512 #[serde(default)]
513 pub description: Option<String>,
514 pub enabled: bool,
515 #[serde(rename = "type")]
516 pub nat_type: String,
517 #[serde(default)]
518 pub interface_id: Option<Uuid>,
519 #[serde(default)]
520 pub protocol: Option<String>,
521 #[serde(default)]
522 pub source: Option<Value>,
523 #[serde(default)]
524 pub destination: Option<Value>,
525 #[serde(default)]
526 pub translated_address: Option<String>,
527 #[serde(default)]
528 pub translated_port: Option<String>,
529 pub metadata: Option<Value>,
530 #[serde(flatten)]
531 pub extra: HashMap<String, Value>,
532}
533
534#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
535#[serde(rename_all = "camelCase")]
536pub struct NatPolicyCreateUpdate {
537 pub name: String,
538 #[serde(skip_serializing_if = "Option::is_none")]
539 pub description: Option<String>,
540 pub enabled: bool,
541 #[serde(rename = "type")]
542 pub nat_type: String,
543 #[serde(skip_serializing_if = "Option::is_none")]
544 pub interface_id: Option<Uuid>,
545 #[serde(skip_serializing_if = "Option::is_none")]
546 pub protocol: Option<String>,
547 #[serde(skip_serializing_if = "Option::is_none")]
548 pub source: Option<Value>,
549 #[serde(skip_serializing_if = "Option::is_none")]
550 pub destination: Option<Value>,
551 #[serde(skip_serializing_if = "Option::is_none")]
552 pub translated_address: Option<String>,
553 #[serde(skip_serializing_if = "Option::is_none")]
554 pub translated_port: Option<String>,
555}
556
557#[cfg(test)]
558#[allow(clippy::unwrap_used)]
559mod tests {
560 use super::{IpAddressFilter, PortFilter, PortItem};
561
562 #[test]
563 fn port_filter_accepts_value_alias_and_numeric_ports() {
564 let filter: PortFilter = serde_json::from_value(serde_json::json!({
565 "type": "VALUE",
566 "items": [
567 { "type": "PORT_NUMBER", "value": 443 },
568 { "type": "PORT_RANGE", "startPort": 8000, "endPort": "9000" }
569 ],
570 "matchOpposite": true
571 }))
572 .unwrap();
573
574 match filter {
575 PortFilter::Ports {
576 items,
577 match_opposite,
578 } => {
579 assert!(match_opposite);
580 assert_eq!(
581 items,
582 vec![
583 PortItem::Number {
584 value: "443".into()
585 },
586 PortItem::Range {
587 start_port: "8000".into(),
588 end_port: "9000".into()
589 },
590 ]
591 );
592 }
593 other => panic!("unexpected filter: {other:?}"),
594 }
595 }
596
597 #[test]
598 fn ip_address_filter_accepts_specific_alias() {
599 let filter: IpAddressFilter = serde_json::from_value(serde_json::json!({
600 "type": "SPECIFIC",
601 "items": [{ "type": "IP_ADDRESS", "value": "192.168.1.10" }],
602 "matchOpposite": false
603 }))
604 .unwrap();
605
606 assert!(matches!(filter, IpAddressFilter::Specific { .. }));
607 }
608}