1use serde::Deserialize;
2
3#[derive(Debug, Deserialize)]
9pub struct ZaiUsageResponse {
10 pub code: i32,
11 pub msg: String,
12 pub data: Option<ZaiQuotaData>,
13 pub success: bool,
14}
15
16#[derive(Debug, Deserialize)]
17pub struct ZaiQuotaData {
18 pub limits: Vec<ZaiLimitRaw>,
19}
20
21impl ZaiQuotaData {
22 #[must_use]
24 pub fn is_limited(&self) -> bool {
25 self.limits.iter().any(|l| l.percentage >= 100)
26 }
27}
28
29#[derive(Debug, Deserialize)]
30pub struct ZaiLimitRaw {
31 #[serde(rename = "type")]
32 pub limit_type: String,
33 pub unit: i32,
34 pub number: i32,
35 pub usage: Option<i64>,
36 pub remaining: Option<i64>,
37 pub percentage: i32,
38 #[serde(rename = "nextResetTime")]
39 pub next_reset_time: Option<i64>,
40}
41
42#[cfg(test)]
43#[expect(clippy::unwrap_used)]
44mod tests {
45 use super::*;
46
47 type TestResult = Result<(), Box<dyn std::error::Error>>;
48
49 #[test]
50 fn test_deserialize_full_response() -> TestResult {
51 let json = r#"{
52 "code": 200,
53 "msg": "ok",
54 "data": {
55 "limits": [
56 {
57 "type": "TOKENS_LIMIT",
58 "unit": 3,
59 "number": 5,
60 "usage": 40000000,
61 "remaining": 26371635,
62 "percentage": 34,
63 "nextResetTime": 1768507567547
64 }
65 ]
66 },
67 "success": true
68 }"#;
69
70 let response: ZaiUsageResponse = serde_json::from_str(json)?;
71 assert!(response.success);
72 assert_eq!(response.code, 200);
73
74 let data = response.data.unwrap();
75 assert_eq!(data.limits.len(), 1);
76 assert!(!data.is_limited());
77 Ok(())
78 }
79
80 #[test]
81 fn test_is_limited_when_percentage_100() -> TestResult {
82 let json = r#"{
83 "code": 200,
84 "msg": "ok",
85 "data": {
86 "limits": [
87 {
88 "type": "TOKENS_LIMIT",
89 "unit": 3,
90 "number": 5,
91 "usage": 50000000,
92 "remaining": 0,
93 "percentage": 100,
94 "nextResetTime": null
95 }
96 ]
97 },
98 "success": true
99 }"#;
100
101 let response: ZaiUsageResponse = serde_json::from_str(json)?;
102 assert!(response.data.unwrap().is_limited());
103 Ok(())
104 }
105
106 #[test]
107 fn test_is_not_limited_when_all_below_100() -> TestResult {
108 let json = r#"{
109 "code": 200,
110 "msg": "ok",
111 "data": {
112 "limits": [
113 {
114 "type": "TOKENS_LIMIT",
115 "unit": 3,
116 "number": 5,
117 "usage": 10000000,
118 "remaining": 40000000,
119 "percentage": 20,
120 "nextResetTime": 1768507567547
121 },
122 {
123 "type": "REQUESTS_LIMIT",
124 "unit": 1,
125 "number": 100,
126 "usage": 30,
127 "remaining": 70,
128 "percentage": 30,
129 "nextResetTime": null
130 }
131 ]
132 },
133 "success": true
134 }"#;
135
136 let response: ZaiUsageResponse = serde_json::from_str(json)?;
137 assert!(!response.data.unwrap().is_limited());
138 Ok(())
139 }
140
141 #[test]
142 fn test_error_response() -> TestResult {
143 let json =
144 r#"{"code": 1302, "msg": "rate limit exceeded", "data": null, "success": false}"#;
145 let response: ZaiUsageResponse = serde_json::from_str(json)?;
146 assert!(!response.success);
147 assert_eq!(response.code, 1302);
148 assert!(response.data.is_none());
149 Ok(())
150 }
151
152 #[test]
153 fn test_empty_limits_is_not_limited() -> TestResult {
154 let json = r#"{"code": 200, "msg": "ok", "data": {"limits": []}, "success": true}"#;
155 let response: ZaiUsageResponse = serde_json::from_str(json)?;
156 let data = response.data.unwrap();
157 assert!(!data.is_limited());
158 Ok(())
159 }
160
161 #[test]
162 fn test_next_reset_time_parsed_as_i64() -> TestResult {
163 let json = r#"{
164 "code": 200,
165 "msg": "ok",
166 "data": {
167 "limits": [{
168 "type": "TOKENS_LIMIT",
169 "unit": 3,
170 "number": 5,
171 "usage": 100,
172 "remaining": 50,
173 "percentage": 50,
174 "nextResetTime": 1768507567547
175 }]
176 },
177 "success": true
178 }"#;
179
180 let response: ZaiUsageResponse = serde_json::from_str(json)?;
181 let limit = &response.data.unwrap().limits[0];
182 assert_eq!(limit.next_reset_time, Some(1_768_507_567_547_i64));
183 Ok(())
184 }
185}