Skip to main content

unifly_api/integration/types/
policy.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7/// Source endpoint of a firewall policy.
8#[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/// Destination endpoint of a firewall policy.
17#[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// ── NAT Policies ────────────────────────────────────────────────────
506
507#[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}