ai_usagebar/deepseek/
types.rs1use serde::Deserialize;
4
5use crate::usage::DeepseekSnapshot;
6
7#[derive(Debug, Clone, Deserialize, Default)]
8#[serde(default)]
9pub struct BalanceResponse {
10 pub is_available: bool,
11 pub balance_infos: Vec<BalanceInfo>,
12}
13
14#[derive(Debug, Clone, Deserialize, Default)]
15#[serde(default)]
16pub struct BalanceInfo {
17 pub currency: String,
18 pub total_balance: String,
19 pub granted_balance: String,
20 pub topped_up_balance: String,
21}
22
23impl BalanceResponse {
24 pub fn into_snapshot(self) -> DeepseekSnapshot {
25 let info = self
27 .balance_infos
28 .iter()
29 .find(|b| b.currency == "USD")
30 .or_else(|| self.balance_infos.iter().find(|b| b.currency == "CNY"))
31 .or_else(|| self.balance_infos.first())
32 .cloned()
33 .unwrap_or_default();
34
35 DeepseekSnapshot {
36 is_available: self.is_available,
37 balance: parse_f64(&info.total_balance),
38 granted: parse_f64(&info.granted_balance),
39 topped_up: parse_f64(&info.topped_up_balance),
40 currency: info.currency,
41 }
42 }
43}
44
45fn parse_f64(s: &str) -> f64 {
46 s.trim().parse::<f64>().unwrap_or(0.0)
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn parses_balance_response() {
55 let raw = r#"{
56 "is_available": true,
57 "balance_infos": [
58 {"currency": "CNY", "total_balance": "10.00", "granted_balance": "10.00", "topped_up_balance": "0.00"},
59 {"currency": "USD", "total_balance": "1.50", "granted_balance": "1.50", "topped_up_balance": "0.00"}
60 ]
61 }"#;
62 let r: BalanceResponse = serde_json::from_str(raw).unwrap();
63 let snap = r.into_snapshot();
64 assert!(snap.is_available);
65 assert_eq!(snap.currency, "USD");
66 assert!((snap.balance - 1.50).abs() < 1e-9);
67 }
68
69 #[test]
70 fn fallback_to_cny_when_no_usd() {
71 let raw = r#"{
72 "is_available": true,
73 "balance_infos": [
74 {"currency": "CNY", "total_balance": "20.00", "granted_balance": "20.00", "topped_up_balance": "0.00"}
75 ]
76 }"#;
77 let r: BalanceResponse = serde_json::from_str(raw).unwrap();
78 let snap = r.into_snapshot();
79 assert_eq!(snap.currency, "CNY");
80 assert!((snap.balance - 20.0).abs() < 1e-9);
81 }
82
83 #[test]
84 fn empty_balance_infos() {
85 let raw = r#"{"is_available": false, "balance_infos": []}"#;
86 let r: BalanceResponse = serde_json::from_str(raw).unwrap();
87 let snap = r.into_snapshot();
88 assert!(!snap.is_available);
89 assert_eq!(snap.balance, 0.0);
90 assert_eq!(snap.currency, "");
91 }
92}