ai_usagebar/openrouter/
types.rs1use serde::Deserialize;
7
8use crate::usage::OpenRouterSnapshot;
9
10#[derive(Debug, Clone, Deserialize)]
12pub struct OrEnvelope<T> {
13 pub data: T,
14}
15
16#[derive(Debug, Clone, Deserialize, Default)]
18#[serde(default)]
19pub struct CreditsData {
20 pub total_credits: f64,
21 pub total_usage: f64,
22}
23
24#[derive(Debug, Clone, Deserialize, Default)]
26#[serde(default)]
27pub struct KeyData {
28 pub label: String,
29 pub limit: Option<f64>,
30 pub limit_remaining: Option<f64>,
31 pub usage: f64,
32 pub usage_daily: f64,
33 pub usage_weekly: f64,
34 pub usage_monthly: f64,
35 pub is_free_tier: bool,
36}
37
38pub fn combine(credits: CreditsData, key: KeyData) -> OpenRouterSnapshot {
40 let label = if key.label.is_empty() {
41 "OpenRouter".to_string()
42 } else {
43 format!("OpenRouter — {}", key.label)
44 };
45 OpenRouterSnapshot {
46 label,
47 total_credits: credits.total_credits,
48 total_usage: credits.total_usage,
49 usage_daily: key.usage_daily,
50 usage_weekly: key.usage_weekly,
51 usage_monthly: key.usage_monthly,
52 is_free_tier: key.is_free_tier,
53 limit: key.limit,
54 limit_remaining: key.limit_remaining,
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn parses_credits_envelope() {
64 let raw = r#"{"data":{"total_credits":100.0,"total_usage":25.5}}"#;
65 let env: OrEnvelope<CreditsData> = serde_json::from_str(raw).unwrap();
66 assert_eq!(env.data.total_credits, 100.0);
67 assert_eq!(env.data.total_usage, 25.5);
68 }
69
70 #[test]
71 fn parses_key_envelope_with_nulls() {
72 let raw = r#"{"data":{
73 "label":"my-key",
74 "limit":null,"limit_remaining":null,
75 "usage":12.34,"usage_daily":1.0,"usage_weekly":3.0,"usage_monthly":12.0,
76 "is_free_tier":false
77 }}"#;
78 let env: OrEnvelope<KeyData> = serde_json::from_str(raw).unwrap();
79 assert_eq!(env.data.label, "my-key");
80 assert!(env.data.limit.is_none());
81 assert_eq!(env.data.usage_monthly, 12.0);
82 assert!(!env.data.is_free_tier);
83 }
84
85 #[test]
86 fn combine_builds_snapshot() {
87 let c = CreditsData {
88 total_credits: 100.0,
89 total_usage: 30.0,
90 };
91 let k = KeyData {
92 label: "key-A".into(),
93 limit: Some(50.0),
94 limit_remaining: Some(20.0),
95 usage: 30.0,
96 usage_daily: 1.0,
97 usage_weekly: 5.0,
98 usage_monthly: 30.0,
99 is_free_tier: false,
100 };
101 let snap = combine(c, k);
102 assert_eq!(snap.label, "OpenRouter — key-A");
103 assert!((snap.balance() - 70.0).abs() < 1e-9);
104 assert_eq!(snap.consumed_pct(), 30);
105 assert_eq!(snap.usage_monthly, 30.0);
106 }
107
108 #[test]
109 fn combine_with_empty_label() {
110 let snap = combine(CreditsData::default(), KeyData::default());
111 assert_eq!(snap.label, "OpenRouter");
112 }
113
114 #[test]
115 fn consumed_pct_handles_zero_credits() {
116 let s = OpenRouterSnapshot {
117 label: "x".into(),
118 total_credits: 0.0,
119 total_usage: 5.0,
120 usage_daily: 0.0,
121 usage_weekly: 0.0,
122 usage_monthly: 0.0,
123 is_free_tier: true,
124 limit: None,
125 limit_remaining: None,
126 };
127 assert_eq!(s.consumed_pct(), 0);
128 }
129}