Skip to main content

seher/openrouter/
types.rs

1use serde::Deserialize;
2
3#[derive(Debug, Deserialize)]
4pub struct CreditsData {
5    pub total_credits: f64,
6    pub total_usage: f64,
7}
8
9impl CreditsData {
10    #[must_use]
11    pub fn is_limited(&self) -> bool {
12        self.total_usage >= self.total_credits
13    }
14
15    #[must_use]
16    pub fn utilization(&self) -> f64 {
17        if self.total_credits > 0.0 {
18            self.total_usage / self.total_credits * 100.0
19        } else {
20            100.0
21        }
22    }
23}
24
25#[derive(Debug, Deserialize)]
26pub struct CreditsResponse {
27    pub data: CreditsData,
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    type TestResult = Result<(), Box<dyn std::error::Error>>;
35
36    #[test]
37    fn test_deserialize_credits_response_with_positive_balance() -> TestResult {
38        // Given: valid API response with credits remaining
39        let json = r#"{"data": {"total_credits": 10.0, "total_usage": 5.0}}"#;
40
41        // When: parsed
42        let response: CreditsResponse = serde_json::from_str(json)?;
43
44        // Then: fields are correctly deserialized
45        assert!((response.data.total_credits - 10.0).abs() < f64::EPSILON);
46        assert!((response.data.total_usage - 5.0).abs() < f64::EPSILON);
47        Ok(())
48    }
49
50    #[test]
51    fn test_is_not_limited_when_usage_is_less_than_credits() -> TestResult {
52        // Given: response where usage is less than credits
53        let json = r#"{"data": {"total_credits": 10.0, "total_usage": 3.0}}"#;
54
55        // When: parsed
56        let response: CreditsResponse = serde_json::from_str(json)?;
57
58        // Then: agent is not limited
59        assert!(!response.data.is_limited());
60        Ok(())
61    }
62
63    #[test]
64    fn test_is_limited_when_usage_equals_credits() -> TestResult {
65        // Given: response where all credits are exactly consumed
66        let json = r#"{"data": {"total_credits": 5.0, "total_usage": 5.0}}"#;
67
68        // When: parsed
69        let response: CreditsResponse = serde_json::from_str(json)?;
70
71        // Then: agent is limited
72        assert!(response.data.is_limited());
73        Ok(())
74    }
75
76    #[test]
77    fn test_is_limited_when_usage_exceeds_credits() -> TestResult {
78        // Given: response where usage exceeds credits (overrun)
79        let json = r#"{"data": {"total_credits": 5.0, "total_usage": 7.5}}"#;
80
81        // When: parsed
82        let response: CreditsResponse = serde_json::from_str(json)?;
83
84        // Then: agent is limited
85        assert!(response.data.is_limited());
86        Ok(())
87    }
88
89    #[test]
90    fn test_is_limited_when_zero_credits() -> TestResult {
91        // Given: free-tier or exhausted account with no credits at all
92        let json = r#"{"data": {"total_credits": 0.0, "total_usage": 0.0}}"#;
93
94        // When: parsed
95        let response: CreditsResponse = serde_json::from_str(json)?;
96
97        // Then: agent is limited
98        assert!(response.data.is_limited());
99        Ok(())
100    }
101
102    #[test]
103    fn test_deserialize_credits_response_integer_values_as_floats() -> TestResult {
104        // Given: API may return integers where floats are expected
105        let json = r#"{"data": {"total_credits": 10, "total_usage": 0}}"#;
106
107        // When: parsed
108        let response: CreditsResponse = serde_json::from_str(json)?;
109
110        // Then: values are correctly read as f64
111        assert!((response.data.total_credits - 10.0).abs() < f64::EPSILON);
112        assert!((response.data.total_usage - 0.0).abs() < f64::EPSILON);
113        Ok(())
114    }
115}