Skip to main content

unifly_api/command/
requests.rs

1// ── Typed request structs for Command payloads ──
2//
3// Every Command variant that previously took `serde_json::Value`
4// now uses one of these strongly-typed request structs instead.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::model::{
10    DnsPolicyType, EntityId, FirewallAction, NetworkManagement, NetworkPurpose, WifiSecurityMode,
11};
12
13// ── Network ────────────────────────────────────────────────────────
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[allow(clippy::struct_excessive_bools)]
17pub struct CreateNetworkRequest {
18    pub name: String,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub vlan_id: Option<u16>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub subnet: Option<String>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub management: Option<NetworkManagement>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub purpose: Option<NetworkPurpose>,
27    pub dhcp_enabled: bool,
28    pub enabled: bool,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub dhcp_range_start: Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub dhcp_range_stop: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub dhcp_lease_time: Option<u32>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub firewall_zone_id: Option<String>,
37    pub isolation_enabled: bool,
38    pub internet_access_enabled: bool,
39}
40
41#[derive(Debug, Clone, Default, Serialize, Deserialize)]
42#[allow(clippy::struct_excessive_bools)]
43pub struct UpdateNetworkRequest {
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub name: Option<String>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub vlan_id: Option<u16>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub subnet: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub dhcp_enabled: Option<bool>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub enabled: Option<bool>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub isolation_enabled: Option<bool>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub internet_access_enabled: Option<bool>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub mdns_forwarding_enabled: Option<bool>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub ipv6_enabled: Option<bool>,
62}
63
64// ── WiFi ───────────────────────────────────────────────────────────
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67#[allow(clippy::struct_excessive_bools)]
68pub struct CreateWifiBroadcastRequest {
69    pub name: String,
70    pub ssid: String,
71    pub security_mode: WifiSecurityMode,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub passphrase: Option<String>,
74    pub enabled: bool,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub network_id: Option<EntityId>,
77    #[serde(alias = "hideName")]
78    pub hide_ssid: bool,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub broadcast_type: Option<String>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    #[serde(alias = "broadcastingFrequenciesGHz")]
83    pub frequencies_ghz: Option<Vec<f32>>,
84    #[serde(default)]
85    #[serde(alias = "bandSteeringEnabled")]
86    pub band_steering: bool,
87    #[serde(default)]
88    #[serde(alias = "bssTransitionEnabled")]
89    pub fast_roaming: bool,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93pub struct UpdateWifiBroadcastRequest {
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub name: Option<String>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub ssid: Option<String>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub security_mode: Option<WifiSecurityMode>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub passphrase: Option<String>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub enabled: Option<bool>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    #[serde(alias = "hideName")]
106    pub hide_ssid: Option<bool>,
107}
108
109// ── Firewall Policy ────────────────────────────────────────────────
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct CreateFirewallPolicyRequest {
113    pub name: String,
114    pub action: FirewallAction,
115    pub source_zone_id: EntityId,
116    pub destination_zone_id: EntityId,
117    pub enabled: bool,
118    pub logging_enabled: bool,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub description: Option<String>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub ip_version: Option<String>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub connection_states: Option<Vec<String>>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub source_filter: Option<TrafficFilterSpec>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub destination_filter: Option<TrafficFilterSpec>,
129}
130
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
132pub struct UpdateFirewallPolicyRequest {
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub name: Option<String>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub action: Option<FirewallAction>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub enabled: Option<bool>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub description: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub ip_version: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub connection_states: Option<Vec<String>>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub source_filter: Option<TrafficFilterSpec>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub destination_filter: Option<TrafficFilterSpec>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub logging_enabled: Option<bool>,
151}
152
153/// Specification for building a traffic filter (used in create/update commands).
154#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(tag = "type", rename_all = "snake_case")]
156pub enum TrafficFilterSpec {
157    /// Filter by network IDs.
158    Network {
159        network_ids: Vec<String>,
160        #[serde(default)]
161        match_opposite: bool,
162    },
163    /// Filter by IP addresses (supports IPs, CIDRs, and ranges).
164    IpAddress {
165        addresses: Vec<String>,
166        #[serde(default)]
167        match_opposite: bool,
168    },
169    /// Filter by ports (supports single ports and ranges like "8000-9000").
170    Port {
171        ports: Vec<String>,
172        #[serde(default)]
173        match_opposite: bool,
174    },
175}
176
177// ── Firewall Zone ──────────────────────────────────────────────────
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct CreateFirewallZoneRequest {
181    pub name: String,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub description: Option<String>,
184    pub network_ids: Vec<EntityId>,
185}
186
187#[derive(Debug, Clone, Default, Serialize, Deserialize)]
188pub struct UpdateFirewallZoneRequest {
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub name: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub description: Option<String>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub network_ids: Option<Vec<EntityId>>,
195}
196
197// ── ACL Rule ───────────────────────────────────────────────────────
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct CreateAclRuleRequest {
201    pub name: String,
202    #[serde(default = "default_acl_rule_type")]
203    pub rule_type: String,
204    pub action: FirewallAction,
205    pub source_zone_id: EntityId,
206    pub destination_zone_id: EntityId,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub protocol: Option<String>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub source_port: Option<String>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub destination_port: Option<String>,
213    pub enabled: bool,
214}
215
216fn default_acl_rule_type() -> String {
217    "IPV4".into()
218}
219
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
221pub struct UpdateAclRuleRequest {
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub name: Option<String>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub action: Option<FirewallAction>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub enabled: Option<bool>,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub protocol: Option<String>,
230}
231
232// ── DNS Policy ─────────────────────────────────────────────────────
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct CreateDnsPolicyRequest {
236    pub name: String,
237    pub policy_type: DnsPolicyType,
238    pub enabled: bool,
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub domain: Option<String>,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub domains: Option<Vec<String>>,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub upstream: Option<String>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub value: Option<String>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    #[serde(alias = "ttlSeconds")]
249    pub ttl_seconds: Option<u32>,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub priority: Option<u16>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    #[serde(alias = "ipv4Address")]
254    pub ipv4_address: Option<String>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    #[serde(alias = "ipv6Address")]
257    pub ipv6_address: Option<String>,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    #[serde(alias = "targetDomain")]
260    pub target_domain: Option<String>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    #[serde(alias = "mailServerDomain")]
263    pub mail_server_domain: Option<String>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub text: Option<String>,
266    #[serde(skip_serializing_if = "Option::is_none")]
267    #[serde(alias = "ipAddress")]
268    pub ip_address: Option<String>,
269    #[serde(skip_serializing_if = "Option::is_none")]
270    #[serde(alias = "serverDomain")]
271    pub server_domain: Option<String>,
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub service: Option<String>,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub protocol: Option<String>,
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub port: Option<u16>,
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub weight: Option<u16>,
280}
281
282#[derive(Debug, Clone, Default, Serialize, Deserialize)]
283pub struct UpdateDnsPolicyRequest {
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub name: Option<String>,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub enabled: Option<bool>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub domain: Option<String>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub domains: Option<Vec<String>>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub upstream: Option<String>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub value: Option<String>,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    #[serde(alias = "ttlSeconds")]
298    pub ttl_seconds: Option<u32>,
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub priority: Option<u16>,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    #[serde(alias = "ipv4Address")]
303    pub ipv4_address: Option<String>,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    #[serde(alias = "ipv6Address")]
306    pub ipv6_address: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    #[serde(alias = "targetDomain")]
309    pub target_domain: Option<String>,
310    #[serde(skip_serializing_if = "Option::is_none")]
311    #[serde(alias = "mailServerDomain")]
312    pub mail_server_domain: Option<String>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub text: Option<String>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    #[serde(alias = "ipAddress")]
317    pub ip_address: Option<String>,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    #[serde(alias = "serverDomain")]
320    pub server_domain: Option<String>,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub service: Option<String>,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub protocol: Option<String>,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub port: Option<u16>,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub weight: Option<u16>,
329}
330
331// ── Traffic Matching List ──────────────────────────────────────────
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct CreateTrafficMatchingListRequest {
335    pub name: String,
336    #[serde(default = "default_traffic_list_type")]
337    pub list_type: String,
338    pub entries: Vec<String>,
339    #[serde(default)]
340    #[serde(skip_serializing_if = "Option::is_none")]
341    #[serde(alias = "items")]
342    pub raw_items: Option<Vec<Value>>,
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub description: Option<String>,
345}
346
347#[derive(Debug, Clone, Default, Serialize, Deserialize)]
348pub struct UpdateTrafficMatchingListRequest {
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub name: Option<String>,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub entries: Option<Vec<String>>,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    #[serde(alias = "items")]
355    pub raw_items: Option<Vec<Value>>,
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub description: Option<String>,
358}
359
360fn default_traffic_list_type() -> String {
361    "IPV4".into()
362}
363
364// ── Vouchers ───────────────────────────────────────────────────────
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct CreateVouchersRequest {
368    pub count: u32,
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub name: Option<String>,
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub time_limit_minutes: Option<u32>,
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub data_usage_limit_mb: Option<u64>,
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub rx_rate_limit_kbps: Option<u64>,
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub tx_rate_limit_kbps: Option<u64>,
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub authorized_guest_limit: Option<u32>,
381}