Skip to main content

unifly_api/command/requests/
policy.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::model::{EntityId, FirewallAction};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct CreateFirewallPolicyRequest {
8    pub name: String,
9    pub action: FirewallAction,
10    #[serde(alias = "source_zone")]
11    pub source_zone_id: EntityId,
12    #[serde(alias = "dest_zone")]
13    pub destination_zone_id: EntityId,
14    #[serde(default = "default_true")]
15    pub enabled: bool,
16    #[serde(default)]
17    pub logging_enabled: bool,
18    #[serde(default = "default_true")]
19    pub allow_return_traffic: bool,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub description: Option<String>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub ip_version: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub connection_states: Option<Vec<String>>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub source_filter: Option<TrafficFilterSpec>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub destination_filter: Option<TrafficFilterSpec>,
30}
31
32fn default_true() -> bool {
33    true
34}
35
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
37pub struct UpdateFirewallPolicyRequest {
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub name: Option<String>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub action: Option<FirewallAction>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub allow_return_traffic: Option<bool>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub enabled: Option<bool>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub description: Option<String>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub ip_version: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub connection_states: Option<Vec<String>>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub source_filter: Option<TrafficFilterSpec>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub destination_filter: Option<TrafficFilterSpec>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub logging_enabled: Option<bool>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(tag = "type", rename_all = "snake_case")]
62pub enum TrafficFilterSpec {
63    Network {
64        network_ids: Vec<String>,
65        #[serde(default)]
66        match_opposite: bool,
67    },
68    IpAddress {
69        addresses: Vec<String>,
70        #[serde(default)]
71        match_opposite: bool,
72    },
73    Port {
74        ports: Vec<String>,
75        #[serde(default)]
76        match_opposite: bool,
77    },
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CreateFirewallZoneRequest {
82    pub name: String,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub description: Option<String>,
85    #[serde(alias = "networks")]
86    pub network_ids: Vec<EntityId>,
87}
88
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
90pub struct UpdateFirewallZoneRequest {
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub name: Option<String>,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub description: Option<String>,
95    #[serde(skip_serializing_if = "Option::is_none", alias = "networks")]
96    pub network_ids: Option<Vec<EntityId>>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct CreateAclRuleRequest {
101    pub name: String,
102    #[serde(default = "default_acl_rule_type")]
103    pub rule_type: String,
104    pub action: FirewallAction,
105    #[serde(alias = "source_zone")]
106    pub source_zone_id: EntityId,
107    #[serde(alias = "dest_zone")]
108    pub destination_zone_id: EntityId,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub description: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub protocol: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none", alias = "src_port")]
114    pub source_port: Option<String>,
115    #[serde(skip_serializing_if = "Option::is_none", alias = "dst_port")]
116    pub destination_port: Option<String>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub source_filter: Option<TrafficFilterSpec>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub destination_filter: Option<TrafficFilterSpec>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub enforcing_device_filter: Option<Value>,
123    #[serde(default = "default_true")]
124    pub enabled: bool,
125}
126
127fn default_acl_rule_type() -> String {
128    "IP".into()
129}
130
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
132pub struct UpdateAclRuleRequest {
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub name: Option<String>,
135    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
136    pub rule_type: Option<String>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub action: Option<FirewallAction>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub enabled: Option<bool>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub description: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none", alias = "source_zone")]
144    pub source_zone_id: Option<EntityId>,
145    #[serde(skip_serializing_if = "Option::is_none", alias = "dest_zone")]
146    pub destination_zone_id: Option<EntityId>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub protocol: Option<String>,
149    #[serde(skip_serializing_if = "Option::is_none", alias = "src_port")]
150    pub source_port: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none", alias = "dst_port")]
152    pub destination_port: Option<String>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub source_filter: Option<TrafficFilterSpec>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub destination_filter: Option<TrafficFilterSpec>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub enforcing_device_filter: Option<Value>,
159}
160
161// ── NAT Policy ──────────────────────────────────────────────────
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct CreateNatPolicyRequest {
165    pub name: String,
166    /// masquerade | source | destination
167    #[serde(rename = "type", alias = "nat_type")]
168    pub nat_type: String,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub description: Option<String>,
171    #[serde(default = "default_true")]
172    pub enabled: bool,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub interface_id: Option<EntityId>,
175    /// tcp | udp | tcp_udp | all
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub protocol: Option<String>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub src_address: Option<String>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub src_port: Option<String>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub dst_address: Option<String>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub dst_port: Option<String>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub translated_address: Option<String>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub translated_port: Option<String>,
190}
191
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct UpdateNatPolicyRequest {
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub name: Option<String>,
196    /// masquerade | source | destination
197    #[serde(
198        rename = "type",
199        alias = "nat_type",
200        skip_serializing_if = "Option::is_none"
201    )]
202    pub nat_type: Option<String>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub description: Option<String>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub enabled: Option<bool>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub interface_id: Option<EntityId>,
209    /// tcp | udp | tcp_udp | all
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub protocol: Option<String>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub src_address: Option<String>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub src_port: Option<String>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub dst_address: Option<String>,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub dst_port: Option<String>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub translated_address: Option<String>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub translated_port: Option<String>,
224}
225
226#[cfg(test)]
227mod tests {
228    use super::{CreateAclRuleRequest, UpdateAclRuleRequest};
229    use crate::model::FirewallAction;
230
231    #[test]
232    fn create_acl_rule_request_defaults_rule_type() {
233        let request: CreateAclRuleRequest = serde_json::from_value(serde_json::json!({
234            "name": "Allow IoT",
235            "action": "Allow",
236            "source_zone_id": "iot",
237            "destination_zone_id": "lan",
238            "enabled": true
239        }))
240        .expect("acl rule request should deserialize");
241
242        assert_eq!(request.rule_type, "IP");
243    }
244
245    #[test]
246    fn update_acl_rule_request_serializes_type_field() {
247        let request = UpdateAclRuleRequest {
248            rule_type: Some("DEVICE".into()),
249            action: Some(FirewallAction::Allow),
250            ..Default::default()
251        };
252
253        let value = serde_json::to_value(&request).expect("acl rule request should serialize");
254        assert_eq!(
255            value.get("type").and_then(serde_json::Value::as_str),
256            Some("DEVICE")
257        );
258        assert_eq!(value.get("rule_type"), None);
259    }
260}