Skip to main content

seher/glm/
types.rs

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()); // TIME_LIMIT has percentage=100
102        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}