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#[cfg(test)]
193mod tests {
194    use super::{CreateAclRuleRequest, UpdateAclRuleRequest};
195    use crate::model::FirewallAction;
196
197    #[test]
198    fn create_acl_rule_request_defaults_rule_type() {
199        let request: CreateAclRuleRequest = serde_json::from_value(serde_json::json!({
200            "name": "Allow IoT",
201            "action": "Allow",
202            "source_zone_id": "iot",
203            "destination_zone_id": "lan",
204            "enabled": true
205        }))
206        .expect("acl rule request should deserialize");
207
208        assert_eq!(request.rule_type, "IP");
209    }
210
211    #[test]
212    fn update_acl_rule_request_serializes_type_field() {
213        let request = UpdateAclRuleRequest {
214            rule_type: Some("DEVICE".into()),
215            action: Some(FirewallAction::Allow),
216            ..Default::default()
217        };
218
219        let value = serde_json::to_value(&request).expect("acl rule request should serialize");
220        assert_eq!(
221            value.get("type").and_then(serde_json::Value::as_str),
222            Some("DEVICE")
223        );
224        assert_eq!(value.get("rule_type"), None);
225    }
226}