1use serde::Deserialize;
2
3#[derive(Debug, Deserialize)]
4pub struct GlmUsageResponse {
5 pub code: i32,
6 pub msg: String,
7 pub data: Option<GlmQuotaData>,
8 pub success: bool,
9}
10
11#[derive(Debug, Deserialize)]
12pub struct GlmQuotaData {
13 pub limits: Vec<GlmLimitRaw>,
14 #[serde(
15 alias = "planName",
16 alias = "plan",
17 alias = "plan_type",
18 alias = "packageName"
19 )]
20 pub plan_name: Option<String>,
21}
22
23impl GlmQuotaData {
24 #[must_use]
25 pub fn is_limited(&self) -> bool {
26 self.limits.iter().any(|l| l.percentage >= 100)
27 }
28}
29
30#[derive(Debug, Deserialize)]
31pub struct GlmLimitRaw {
32 #[serde(rename = "type")]
33 pub limit_type: String,
34 pub unit: i32,
35 pub number: i32,
36 pub usage: Option<i64>,
37 #[serde(rename = "currentValue")]
38 pub current_value: Option<i64>,
39 pub remaining: Option<i64>,
40 pub percentage: i32,
41 #[serde(default, rename = "usageDetails")]
42 pub usage_details: Vec<GlmUsageDetail>,
43 #[serde(rename = "nextResetTime")]
44 pub next_reset_time: Option<i64>,
45}
46
47#[derive(Debug, Deserialize)]
48pub struct GlmUsageDetail {
49 #[serde(rename = "modelCode")]
50 pub model_code: Option<String>,
51 pub usage: Option<i64>,
52}
53
54#[cfg(test)]
55#[expect(clippy::unwrap_used)]
56mod tests {
57 use super::*;
58
59 type TestResult = Result<(), Box<dyn std::error::Error>>;
60
61 #[test]
62 fn test_deserialize_full_response() -> TestResult {
63 let json = r#"{
64 "code": 200,
65 "msg": "Operation successful",
66 "data": {
67 "limits": [
68 {
69 "type": "TIME_LIMIT",
70 "unit": 5,
71 "number": 1,
72 "usage": 100,
73 "currentValue": 102,
74 "remaining": 0,
75 "percentage": 100,
76 "usageDetails": [{"modelCode": "search-prime", "usage": 95}]
77 },
78 {
79 "type": "TOKENS_LIMIT",
80 "unit": 3,
81 "number": 5,
82 "usage": 40000000,
83 "currentValue": 13628365,
84 "remaining": 26371635,
85 "percentage": 34,
86 "nextResetTime": 1768507567547
87 }
88 ],
89 "planName": "Pro"
90 },
91 "success": true
92 }"#;
93
94 let response: GlmUsageResponse = serde_json::from_str(json)?;
95 assert!(response.success);
96 assert_eq!(response.code, 200);
97
98 let data = response.data.unwrap();
99 assert_eq!(data.plan_name.as_deref(), Some("Pro"));
100 assert_eq!(data.limits.len(), 2);
101 assert!(data.is_limited()); Ok(())
103 }
104
105 #[test]
106 fn test_is_limited_when_percentage_100() -> TestResult {
107 let json = r#"{
108 "code": 200,
109 "msg": "ok",
110 "data": {
111 "limits": [
112 {
113 "type": "TOKENS_LIMIT",
114 "unit": 3,
115 "number": 5,
116 "usage": 100,
117 "currentValue": 100,
118 "remaining": 0,
119 "percentage": 100,
120 "usageDetails": []
121 }
122 ]
123 },
124 "success": true
125 }"#;
126
127 let response: GlmUsageResponse = serde_json::from_str(json)?;
128 assert!(response.data.unwrap().is_limited());
129 Ok(())
130 }
131
132 #[test]
133 fn test_is_not_limited_when_below_100() -> TestResult {
134 let json = r#"{
135 "code": 200,
136 "msg": "ok",
137 "data": {
138 "limits": [
139 {
140 "type": "TOKENS_LIMIT",
141 "unit": 3,
142 "number": 5,
143 "usage": 100,
144 "currentValue": 50,
145 "remaining": 50,
146 "percentage": 50,
147 "usageDetails": []
148 }
149 ]
150 },
151 "success": true
152 }"#;
153
154 let response: GlmUsageResponse = serde_json::from_str(json)?;
155 assert!(!response.data.unwrap().is_limited());
156 Ok(())
157 }
158
159 #[test]
160 fn test_plan_name_aliases() -> TestResult {
161 for alias in &["planName", "plan", "plan_type", "packageName"] {
162 let json = format!(
163 r#"{{"code": 200, "msg": "ok", "data": {{"limits": [], "{alias}": "Lite"}}, "success": true}}"#
164 );
165 let response: GlmUsageResponse = serde_json::from_str(&json)?;
166 assert_eq!(
167 response.data.unwrap().plan_name.as_deref(),
168 Some("Lite"),
169 "Failed for alias: {alias}"
170 );
171 }
172 Ok(())
173 }
174
175 #[test]
176 fn test_error_response() -> TestResult {
177 let json =
178 r#"{"code": 1302, "msg": "rate limit exceeded", "data": null, "success": false}"#;
179 let response: GlmUsageResponse = serde_json::from_str(json)?;
180 assert!(!response.success);
181 assert_eq!(response.code, 1302);
182 assert!(response.data.is_none());
183 Ok(())
184 }
185
186 #[test]
187 fn test_empty_limits() -> TestResult {
188 let json = r#"{"code": 200, "msg": "ok", "data": {"limits": []}, "success": true}"#;
189 let response: GlmUsageResponse = serde_json::from_str(json)?;
190 let data = response.data.unwrap();
191 assert!(!data.is_limited());
192 Ok(())
193 }
194
195 #[test]
196 fn test_usage_details_deserialize() -> TestResult {
197 let json = r#"{
198 "code": 200,
199 "msg": "ok",
200 "data": {
201 "limits": [{
202 "type": "TIME_LIMIT",
203 "unit": 5,
204 "number": 1,
205 "usage": 100,
206 "currentValue": 50,
207 "remaining": 50,
208 "percentage": 50,
209 "usageDetails": [
210 {"modelCode": "chatglm-pro", "usage": 30},
211 {"modelCode": "search-prime", "usage": 20}
212 ]
213 }]
214 },
215 "success": true
216 }"#;
217
218 let response: GlmUsageResponse = serde_json::from_str(json)?;
219 let limit = &response.data.unwrap().limits[0];
220 assert_eq!(limit.usage_details.len(), 2);
221 assert_eq!(
222 limit.usage_details[0].model_code.as_deref(),
223 Some("chatglm-pro")
224 );
225 assert_eq!(limit.usage_details[0].usage, Some(30));
226 Ok(())
227 }
228}