Skip to main content

unifly_api/integration/
types.rs

1//! Integration API response types for the UniFi Network Integration API (v10.1.84).
2//!
3//! All types match the JSON responses from `/integration/v1/` endpoints.
4//! Field names use camelCase via `#[serde(rename_all = "camelCase")]`.
5
6use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use uuid::Uuid;
11
12// ── Pagination ───────────────────────────────────────────────────────
13
14/// Generic pagination wrapper returned by all list endpoints.
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct Page<T> {
18    pub offset: i64,
19    pub limit: i32,
20    pub count: i32,
21    pub total_count: i64,
22    pub data: Vec<T>,
23}
24
25// ── Sites ────────────────────────────────────────────────────────────
26
27/// Site overview — from `GET /v1/sites`.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct SiteResponse {
31    pub id: Uuid,
32    pub name: String,
33    /// Used as the Legacy API site name (`/api/s/{internalReference}/`).
34    pub internal_reference: String,
35}
36
37// ── Devices ──────────────────────────────────────────────────────────
38
39/// Adopted device overview — from `GET /v1/sites/{siteId}/devices`.
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct DeviceResponse {
43    pub id: Uuid,
44    pub mac_address: String,
45    pub ip_address: Option<String>,
46    pub name: String,
47    pub model: String,
48    /// One of: `ONLINE`, `OFFLINE`, `PENDING_ADOPTION`, `UPDATING`,
49    /// `GETTING_READY`, `ADOPTING`, `DELETING`, `CONNECTION_INTERRUPTED`, `ISOLATED`.
50    pub state: String,
51    pub supported: bool,
52    pub firmware_version: Option<String>,
53    pub firmware_updatable: bool,
54    pub features: Vec<String>,
55    /// Complex nested interfaces object — kept as opaque JSON.
56    pub interfaces: Value,
57}
58
59/// Adopted device details — extends overview with additional fields.
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct DeviceDetailsResponse {
63    pub id: Uuid,
64    pub mac_address: String,
65    pub ip_address: Option<String>,
66    pub name: String,
67    pub model: String,
68    pub state: String,
69    pub supported: bool,
70    pub firmware_version: Option<String>,
71    pub firmware_updatable: bool,
72    pub features: Vec<String>,
73    pub interfaces: Value,
74    pub serial_number: Option<String>,
75    pub short_name: Option<String>,
76    /// ISO 8601 date-time.
77    pub startup_timestamp: Option<String>,
78    /// Catch-all for additional fields not modeled above.
79    #[serde(flatten)]
80    pub extra: HashMap<String, Value>,
81}
82
83/// Latest statistics for a device — from `GET /v1/sites/{siteId}/devices/{deviceId}/statistics/latest`.
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct DeviceStatisticsResponse {
87    pub uptime_sec: Option<i64>,
88    pub cpu_utilization_pct: Option<f64>,
89    pub memory_utilization_pct: Option<f64>,
90    pub load_average_1_min: Option<f64>,
91    pub load_average_5_min: Option<f64>,
92    pub load_average_15_min: Option<f64>,
93    /// ISO 8601 date-time.
94    pub last_heartbeat_at: Option<String>,
95    /// ISO 8601 date-time.
96    pub next_heartbeat_at: Option<String>,
97    /// Complex nested interfaces statistics.
98    pub interfaces: Value,
99    /// Uplink information.
100    pub uplink: Option<Value>,
101}
102
103// ── Clients ──────────────────────────────────────────────────────────
104
105/// Client overview — from `GET /v1/sites/{siteId}/clients`.
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct ClientResponse {
109    pub id: Uuid,
110    pub name: String,
111    /// One of: `WIRED`, `WIRELESS`, `VPN`, `TELEPORT`.
112    #[serde(rename = "type")]
113    pub client_type: String,
114    pub ip_address: Option<String>,
115    /// ISO 8601 date-time.
116    pub connected_at: Option<String>,
117    /// Polymorphic access object — contains a `type` discriminator field.
118    pub access: Value,
119}
120
121/// Client details — extends overview with additional fields.
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct ClientDetailsResponse {
125    pub id: Uuid,
126    pub name: String,
127    #[serde(rename = "type")]
128    pub client_type: String,
129    pub ip_address: Option<String>,
130    pub connected_at: Option<String>,
131    pub access: Value,
132    /// Catch-all for additional fields not modeled above.
133    #[serde(flatten)]
134    pub extra: HashMap<String, Value>,
135}
136
137// ── Networks ─────────────────────────────────────────────────────────
138
139/// Network overview — from `GET /v1/sites/{siteId}/networks`.
140///
141/// GATEWAY networks include top-level fields like `isolationEnabled`,
142/// `internetAccessEnabled`, `ipv4Configuration`, `ipv6Configuration`, etc.
143/// These are captured in `extra` via `#[serde(flatten)]`.
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145#[serde(rename_all = "camelCase")]
146pub struct NetworkResponse {
147    pub id: Uuid,
148    pub name: String,
149    pub enabled: bool,
150    /// One of: `USER_DEFINED`, `SYSTEM_DEFINED`, `ORCHESTRATED`.
151    pub management: String,
152    pub vlan_id: i32,
153    #[serde(default)]
154    pub default: bool,
155    pub metadata: Value,
156    /// Catch-all for management-type-specific fields (GATEWAY: ipv4Configuration,
157    /// ipv6Configuration, isolationEnabled, etc.; SWITCH: deviceId, natOutbound, etc.)
158    #[serde(flatten)]
159    pub extra: HashMap<String, Value>,
160}
161
162/// Network details — extends overview with additional fields.
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct NetworkDetailsResponse {
166    pub id: Uuid,
167    pub name: String,
168    pub enabled: bool,
169    pub management: String,
170    pub vlan_id: i32,
171    #[serde(default)]
172    pub default: bool,
173    pub metadata: Value,
174    pub dhcp_guarding: Option<Value>,
175    /// Catch-all for management-type-specific fields.
176    #[serde(flatten)]
177    pub extra: HashMap<String, Value>,
178}
179
180/// Create or update a network.
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct NetworkCreateUpdate {
184    pub name: String,
185    pub enabled: bool,
186    pub management: String,
187    pub vlan_id: i32,
188    pub dhcp_guarding: Option<Value>,
189    /// GATEWAY/SWITCH-specific fields to include in create/update requests.
190    #[serde(flatten)]
191    pub extra: HashMap<String, Value>,
192}
193
194/// References to resources using a network.
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196#[serde(rename_all = "camelCase")]
197pub struct NetworkReferencesResponse {
198    #[serde(flatten)]
199    pub fields: HashMap<String, Value>,
200}
201
202// ── WiFi Broadcasts ──────────────────────────────────────────────────
203
204/// WiFi broadcast overview — from `GET /v1/sites/{siteId}/wifi`.
205#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct WifiBroadcastResponse {
208    pub id: Uuid,
209    pub name: String,
210    #[serde(rename = "type")]
211    pub broadcast_type: String,
212    pub enabled: bool,
213    pub security_configuration: Value,
214    pub metadata: Value,
215    pub network: Option<Value>,
216    pub broadcasting_device_filter: Option<Value>,
217}
218
219/// WiFi broadcast details — extends overview with additional fields.
220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct WifiBroadcastDetailsResponse {
223    pub id: Uuid,
224    pub name: String,
225    #[serde(rename = "type")]
226    pub broadcast_type: String,
227    pub enabled: bool,
228    pub security_configuration: Value,
229    pub metadata: Value,
230    pub network: Option<Value>,
231    pub broadcasting_device_filter: Option<Value>,
232    /// Catch-all for additional fields not modeled above.
233    #[serde(flatten)]
234    pub extra: HashMap<String, Value>,
235}
236
237/// Create or update a WiFi broadcast.
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct WifiBroadcastCreateUpdate {
241    pub name: String,
242    #[serde(rename = "type")]
243    pub broadcast_type: String,
244    pub enabled: bool,
245    /// All remaining type-specific fields.
246    #[serde(flatten)]
247    pub body: serde_json::Map<String, Value>,
248}
249
250// ── Firewall Policies ────────────────────────────────────────────────
251
252/// Firewall policy — from `GET /v1/sites/{siteId}/firewall/policies`.
253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct FirewallPolicyResponse {
256    pub id: Uuid,
257    pub name: String,
258    #[serde(default)]
259    pub description: Option<String>,
260    pub enabled: bool,
261    /// Polymorphic action object with `type` discriminator.
262    pub action: Value,
263    pub ip_protocol_scope: Option<Value>,
264    #[serde(default)]
265    pub logging_enabled: bool,
266    pub metadata: Option<Value>,
267    /// Catch-all for additional / variable fields (index, source, destination,
268    /// sourceFirewallZoneId, destinationFirewallZoneId, schedule, etc.)
269    #[serde(flatten)]
270    pub extra: HashMap<String, Value>,
271}
272
273/// Create or update a firewall policy.
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct FirewallPolicyCreateUpdate {
277    pub name: String,
278    pub description: Option<String>,
279    pub enabled: bool,
280    pub action: Value,
281    pub source: Value,
282    pub destination: Value,
283    pub ip_protocol_scope: Value,
284    pub logging_enabled: bool,
285    pub ipsec_filter: Option<String>,
286    pub schedule: Option<Value>,
287    pub connection_state_filter: Option<Vec<String>>,
288}
289
290/// Patch a firewall policy (partial update).
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct FirewallPolicyPatch {
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub enabled: Option<bool>,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub logging_enabled: Option<bool>,
298}
299
300/// Ordered firewall policy IDs — for reordering policies.
301#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct FirewallPolicyOrdering {
304    pub before_system_defined: Vec<Uuid>,
305    pub after_system_defined: Vec<Uuid>,
306}
307
308// ── Firewall Zones ───────────────────────────────────────────────────
309
310/// Firewall zone — from `GET /v1/sites/{siteId}/firewall/zones`.
311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct FirewallZoneResponse {
314    pub id: Uuid,
315    pub name: String,
316    pub network_ids: Vec<Uuid>,
317    pub metadata: Value,
318}
319
320/// Create or update a firewall zone.
321#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
322#[serde(rename_all = "camelCase")]
323pub struct FirewallZoneCreateUpdate {
324    pub name: String,
325    pub network_ids: Vec<Uuid>,
326}
327
328// ── ACL Rules ────────────────────────────────────────────────────────
329
330/// ACL rule — from `GET /v1/sites/{siteId}/acl/rules`.
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct AclRuleResponse {
334    pub id: Uuid,
335    pub name: String,
336    /// One of: `IP`, `MAC`.
337    #[serde(rename = "type")]
338    pub rule_type: String,
339    /// One of: `ALLOW`, `BLOCK`.
340    pub action: String,
341    pub enabled: bool,
342    pub index: i32,
343    pub description: Option<String>,
344    pub source_filter: Option<Value>,
345    pub destination_filter: Option<Value>,
346    pub enforcing_device_filter: Option<Value>,
347    pub metadata: Value,
348}
349
350/// Create or update an ACL rule.
351#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
352#[serde(rename_all = "camelCase")]
353pub struct AclRuleCreateUpdate {
354    pub name: String,
355    #[serde(rename = "type")]
356    pub rule_type: String,
357    pub action: String,
358    pub enabled: bool,
359    pub description: Option<String>,
360    pub source_filter: Option<Value>,
361    pub destination_filter: Option<Value>,
362    pub enforcing_device_filter: Option<Value>,
363}
364
365/// ACL rule ordering — for reordering rules.
366#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub struct AclRuleOrdering {
369    pub ordered_acl_rule_ids: Vec<Uuid>,
370}
371
372// ── DNS Policies ─────────────────────────────────────────────────────
373
374/// DNS policy — from `GET /v1/sites/{siteId}/dns/policies`.
375#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct DnsPolicyResponse {
378    pub id: Uuid,
379    #[serde(rename = "type")]
380    pub policy_type: String,
381    pub enabled: bool,
382    pub domain: Option<String>,
383    pub metadata: Value,
384    /// Type-specific fields vary by policy type.
385    #[serde(flatten)]
386    pub extra: HashMap<String, Value>,
387}
388
389/// Create or update a DNS policy.
390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
391#[serde(rename_all = "camelCase")]
392pub struct DnsPolicyCreateUpdate {
393    #[serde(rename = "type")]
394    pub policy_type: String,
395    pub enabled: bool,
396    /// Type-specific fields vary by policy type.
397    #[serde(flatten)]
398    pub fields: serde_json::Map<String, Value>,
399}
400
401// ── Traffic Matching Lists ───────────────────────────────────────────
402
403/// Traffic matching list — from `GET /v1/sites/{siteId}/traffic-matching-lists`.
404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405#[serde(rename_all = "camelCase")]
406pub struct TrafficMatchingListResponse {
407    pub id: Uuid,
408    pub name: String,
409    /// One of: `IPV4`, `IPV6`, `PORT`.
410    #[serde(rename = "type")]
411    pub list_type: String,
412    #[serde(flatten)]
413    pub extra: HashMap<String, Value>,
414}
415
416/// Create or update a traffic matching list.
417#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct TrafficMatchingListCreateUpdate {
420    pub name: String,
421    #[serde(rename = "type")]
422    pub list_type: String,
423    #[serde(flatten)]
424    pub fields: serde_json::Map<String, Value>,
425}
426
427// ── Hotspot Vouchers ─────────────────────────────────────────────────
428
429/// Hotspot voucher — from `GET /v1/sites/{siteId}/hotspot/vouchers`.
430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
431#[serde(rename_all = "camelCase")]
432pub struct VoucherResponse {
433    pub id: Uuid,
434    pub code: String,
435    pub name: String,
436    /// ISO 8601 date-time.
437    pub created_at: String,
438    /// ISO 8601 date-time.
439    pub activated_at: Option<String>,
440    /// ISO 8601 date-time.
441    pub expires_at: Option<String>,
442    pub expired: bool,
443    pub time_limit_minutes: i64,
444    pub authorized_guest_count: i64,
445    pub authorized_guest_limit: Option<i64>,
446    pub data_usage_limit_m_bytes: Option<i64>,
447    pub rx_rate_limit_kbps: Option<i64>,
448    pub tx_rate_limit_kbps: Option<i64>,
449}
450
451/// Create hotspot voucher(s).
452#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
453#[serde(rename_all = "camelCase")]
454pub struct VoucherCreateRequest {
455    pub name: String,
456    /// Number of vouchers to create (defaults to 1).
457    pub count: Option<i32>,
458    pub time_limit_minutes: i64,
459    pub authorized_guest_limit: Option<i64>,
460    pub data_usage_limit_m_bytes: Option<i64>,
461    pub rx_rate_limit_kbps: Option<i64>,
462    pub tx_rate_limit_kbps: Option<i64>,
463}
464
465/// Bulk voucher deletion results.
466#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
467#[serde(rename_all = "camelCase")]
468pub struct VoucherDeletionResults {
469    #[serde(flatten)]
470    pub fields: HashMap<String, Value>,
471}
472
473// ── Device Actions ───────────────────────────────────────────────────
474
475/// Device action request body.
476///
477/// Valid actions: `RESTART`, `ADOPT`, `LOCATE_ON`, `LOCATE_OFF`.
478#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
479#[serde(rename_all = "camelCase")]
480pub struct DeviceActionRequest {
481    pub action: String,
482}
483
484/// Device adoption request body.
485#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
486#[serde(rename_all = "camelCase")]
487pub struct DeviceAdoptionRequest {
488    pub mac_address: String,
489    pub ignore_device_limit: bool,
490}
491
492// ── Client Actions ───────────────────────────────────────────────────
493
494/// Client action request body.
495///
496/// Valid actions: `BLOCK`, `UNBLOCK`, `RECONNECT`.
497#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
498#[serde(rename_all = "camelCase")]
499pub struct ClientActionRequest {
500    pub action: String,
501}
502
503/// Client action response.
504#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
505#[serde(rename_all = "camelCase")]
506pub struct ClientActionResponse {
507    pub action: String,
508    pub id: Uuid,
509    #[serde(flatten)]
510    pub extra: HashMap<String, Value>,
511}
512
513// ── Port Actions ─────────────────────────────────────────────────────
514
515/// Port action request body.
516///
517/// Valid actions: `POWER_CYCLE`.
518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
519#[serde(rename_all = "camelCase")]
520pub struct PortActionRequest {
521    pub action: String,
522}
523
524// ── Application Info ─────────────────────────────────────────────────
525
526/// Application info — from `GET /v1/info`.
527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
528#[serde(rename_all = "camelCase")]
529pub struct ApplicationInfoResponse {
530    #[serde(flatten)]
531    pub fields: HashMap<String, Value>,
532}
533
534// ── Error ────────────────────────────────────────────────────────────
535
536/// Error response returned by the Integration API.
537#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase")]
539pub struct ErrorResponse {
540    pub message: Option<String>,
541    #[serde(flatten)]
542    pub extra: HashMap<String, Value>,
543}
544
545// ── Read-only / Opaque Types ─────────────────────────────────────────
546//
547// These endpoints return complex or under-documented structures.
548// We capture them as open-ended maps until we need typed access.
549
550/// DPI category — from `GET /v1/sites/{siteId}/dpi/categories`.
551#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
552#[serde(rename_all = "camelCase")]
553pub struct DpiCategoryResponse {
554    #[serde(flatten)]
555    pub fields: HashMap<String, Value>,
556}
557
558/// DPI application — from `GET /v1/sites/{siteId}/dpi/applications`.
559#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
560#[serde(rename_all = "camelCase")]
561pub struct DpiApplicationResponse {
562    #[serde(flatten)]
563    pub fields: HashMap<String, Value>,
564}
565
566/// VPN server — from `GET /v1/sites/{siteId}/vpn/servers`.
567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
568#[serde(rename_all = "camelCase")]
569pub struct VpnServerResponse {
570    #[serde(flatten)]
571    pub fields: HashMap<String, Value>,
572}
573
574/// VPN tunnel — from `GET /v1/sites/{siteId}/vpn/tunnels`.
575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
576#[serde(rename_all = "camelCase")]
577pub struct VpnTunnelResponse {
578    #[serde(flatten)]
579    pub fields: HashMap<String, Value>,
580}
581
582/// WAN configuration — from `GET /v1/sites/{siteId}/wan`.
583#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
584#[serde(rename_all = "camelCase")]
585pub struct WanResponse {
586    #[serde(flatten)]
587    pub fields: HashMap<String, Value>,
588}
589
590/// RADIUS profile — from `GET /v1/sites/{siteId}/radius/profiles`.
591#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct RadiusProfileResponse {
594    #[serde(flatten)]
595    pub fields: HashMap<String, Value>,
596}
597
598/// Country metadata — from `GET /v1/countries`.
599#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
600#[serde(rename_all = "camelCase")]
601pub struct CountryResponse {
602    #[serde(flatten)]
603    pub fields: HashMap<String, Value>,
604}
605
606/// Device tag — from `GET /v1/sites/{siteId}/devices/tags`.
607#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
608#[serde(rename_all = "camelCase")]
609pub struct DeviceTagResponse {
610    #[serde(flatten)]
611    pub fields: HashMap<String, Value>,
612}
613
614/// Pending device — from `GET /v1/sites/{siteId}/devices/pending`.
615#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616#[serde(rename_all = "camelCase")]
617pub struct PendingDeviceResponse {
618    #[serde(flatten)]
619    pub fields: HashMap<String, Value>,
620}