Skip to main content

unifly_api/legacy/
models.rs

1// Legacy API response types
2//
3// Models for the UniFi controller's legacy JSON API. All responses are wrapped
4// in the `LegacyResponse<T>` envelope. Fields use `#[serde(default)]` liberally
5// because the API is inconsistent about field presence across firmware versions.
6
7use serde::{Deserialize, Serialize};
8
9// ── Response Envelope ────────────────────────────────────────────────
10
11/// Standard UniFi legacy API response envelope.
12///
13/// Every legacy endpoint wraps its payload:
14/// ```json
15/// { "meta": { "rc": "ok", "msg": "optional" }, "data": [...] }
16/// ```
17#[derive(Debug, Deserialize)]
18pub struct LegacyResponse<T> {
19    pub meta: Meta,
20    pub data: Vec<T>,
21}
22
23/// Metadata from the legacy envelope. `rc` == `"ok"` means success.
24#[derive(Debug, Deserialize)]
25pub struct Meta {
26    pub rc: String,
27    #[serde(default)]
28    pub msg: Option<String>,
29}
30
31// ── Device ───────────────────────────────────────────────────────────
32
33/// Full device object from `stat/device`.
34///
35/// The legacy API can return 100+ fields per device. We model the most
36/// commonly needed ones explicitly; everything else lands in `extra`.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct LegacyDevice {
39    #[serde(rename = "_id")]
40    pub id: String,
41    pub mac: String,
42    #[serde(rename = "type")]
43    pub device_type: String,
44    #[serde(default)]
45    pub ip: Option<String>,
46    #[serde(default)]
47    pub name: Option<String>,
48    #[serde(default)]
49    pub model: Option<String>,
50    #[serde(default)]
51    pub version: Option<String>,
52    #[serde(default)]
53    pub adopted: bool,
54    /// 0=offline, 1=online, 2=pending, 4=upgrading, 5=provisioning
55    #[serde(default)]
56    pub state: i32,
57    #[serde(default)]
58    pub sys_stats: Option<SysStats>,
59    #[serde(default)]
60    pub uptime: Option<i64>,
61    #[serde(default)]
62    pub num_sta: Option<i32>,
63    #[serde(default)]
64    pub serial: Option<String>,
65    #[serde(default)]
66    pub site_id: Option<String>,
67    #[serde(default)]
68    pub last_seen: Option<i64>,
69    #[serde(default)]
70    pub upgradable: Option<bool>,
71    #[serde(default, rename = "user-num_sta")]
72    pub user_num_sta: Option<i32>,
73    #[serde(default, rename = "guest-num_sta")]
74    pub guest_num_sta: Option<i32>,
75    /// Catch-all for undocumented fields.
76    #[serde(flatten)]
77    pub extra: serde_json::Map<String, serde_json::Value>,
78}
79
80/// System statistics nested inside `LegacyDevice`.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct SysStats {
83    #[serde(default, rename = "loadavg_1")]
84    pub load_1: Option<String>,
85    #[serde(default, rename = "loadavg_5")]
86    pub load_5: Option<String>,
87    #[serde(default, rename = "loadavg_15")]
88    pub load_15: Option<String>,
89    #[serde(default)]
90    pub mem_total: Option<i64>,
91    #[serde(default)]
92    pub mem_used: Option<i64>,
93    #[serde(default)]
94    pub cpu: Option<String>,
95}
96
97// ── Client (Station) ─────────────────────────────────────────────────
98
99/// Connected client from `stat/sta`.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct LegacyClientEntry {
102    #[serde(rename = "_id")]
103    pub id: String,
104    pub mac: String,
105    #[serde(default)]
106    pub hostname: Option<String>,
107    #[serde(default)]
108    pub ip: Option<String>,
109    #[serde(default)]
110    pub oui: Option<String>,
111    #[serde(default)]
112    pub name: Option<String>,
113    #[serde(default)]
114    pub is_guest: Option<bool>,
115    #[serde(default)]
116    pub is_wired: Option<bool>,
117    #[serde(default)]
118    pub authorized: Option<bool>,
119    #[serde(default)]
120    pub blocked: Option<bool>,
121    #[serde(default)]
122    pub signal: Option<i32>,
123    #[serde(default)]
124    pub tx_bytes: Option<i64>,
125    #[serde(default)]
126    pub rx_bytes: Option<i64>,
127    #[serde(default)]
128    pub tx_rate: Option<i64>,
129    #[serde(default)]
130    pub rx_rate: Option<i64>,
131    #[serde(default)]
132    pub uptime: Option<i64>,
133    #[serde(default)]
134    pub first_seen: Option<i64>,
135    #[serde(default)]
136    pub last_seen: Option<i64>,
137    #[serde(default)]
138    pub site_id: Option<String>,
139    #[serde(default)]
140    pub essid: Option<String>,
141    #[serde(default)]
142    pub bssid: Option<String>,
143    #[serde(default)]
144    pub channel: Option<i32>,
145    #[serde(default)]
146    pub radio: Option<String>,
147    #[serde(default)]
148    pub rssi: Option<i32>,
149    #[serde(default)]
150    pub noise: Option<i32>,
151    #[serde(default)]
152    pub satisfaction: Option<i32>,
153    #[serde(default)]
154    pub ap_mac: Option<String>,
155    #[serde(default)]
156    pub network: Option<String>,
157    #[serde(default)]
158    pub network_id: Option<String>,
159    #[serde(default)]
160    pub sw_mac: Option<String>,
161    #[serde(default)]
162    pub sw_port: Option<i32>,
163    /// Catch-all for undocumented fields.
164    #[serde(flatten)]
165    pub extra: serde_json::Map<String, serde_json::Value>,
166}
167
168// ── Site ─────────────────────────────────────────────────────────────
169
170/// Site object from `/api/self/sites`.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct LegacySite {
173    #[serde(rename = "_id")]
174    pub id: String,
175    pub name: String,
176    #[serde(default)]
177    pub desc: Option<String>,
178    #[serde(default)]
179    pub role: Option<String>,
180    /// Catch-all for undocumented fields.
181    #[serde(flatten)]
182    pub extra: serde_json::Map<String, serde_json::Value>,
183}
184
185// ── Event ────────────────────────────────────────────────────────────
186
187/// Event object from `stat/event`.
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct LegacyEvent {
190    #[serde(rename = "_id")]
191    pub id: String,
192    #[serde(default)]
193    pub key: Option<String>,
194    #[serde(default)]
195    pub msg: Option<String>,
196    #[serde(default)]
197    pub datetime: Option<String>,
198    #[serde(default)]
199    pub subsystem: Option<String>,
200    #[serde(default)]
201    pub site_id: Option<String>,
202    /// Catch-all for undocumented fields.
203    #[serde(flatten)]
204    pub extra: serde_json::Map<String, serde_json::Value>,
205}
206
207// ── Alarm ────────────────────────────────────────────────────────────
208
209/// Alarm object from `stat/alarm`.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct LegacyAlarm {
212    #[serde(rename = "_id")]
213    pub id: String,
214    #[serde(default)]
215    pub key: Option<String>,
216    #[serde(default)]
217    pub msg: Option<String>,
218    #[serde(default)]
219    pub datetime: Option<String>,
220    #[serde(default)]
221    pub archived: Option<bool>,
222    /// Catch-all for undocumented fields.
223    #[serde(flatten)]
224    pub extra: serde_json::Map<String, serde_json::Value>,
225}