Skip to main content

seher/warp/
types.rs

1use serde::Deserialize;
2
3/// Top-level GraphQL response from the Warp API.
4///
5/// The Warp provider uses a GraphQL endpoint with a `GetRequestLimitInfo`
6/// query that returns nested data under the `data` key.
7#[derive(Debug, Deserialize)]
8pub struct WarpLimitInfoResponse {
9    pub data: WarpLimitData,
10}
11
12#[derive(Debug, Deserialize)]
13pub struct WarpLimitData {
14    #[serde(rename = "getRequestLimitInfo")]
15    pub get_request_limit_info: WarpRequestLimitInfo,
16}
17
18/// Usage / limit information returned by the Warp GraphQL API.
19#[derive(Debug, Deserialize)]
20pub struct WarpRequestLimitInfo {
21    /// Maximum number of requests allowed in the current window.
22    pub limit: i64,
23    /// Number of requests consumed so far.
24    pub used: i64,
25    /// Seconds until the window resets.  `None` if no reset is applicable.
26    #[serde(rename = "resetInSeconds")]
27    pub reset_in_seconds: Option<i64>,
28}
29
30impl WarpRequestLimitInfo {
31    /// Returns `true` when the usage has reached or exceeded the limit.
32    #[must_use]
33    pub fn is_limited(&self) -> bool {
34        self.used >= self.limit
35    }
36
37    /// Returns usage as a percentage (0–100). Returns 100.0 when limit is
38    /// zero or negative.
39    #[must_use]
40    #[expect(clippy::cast_precision_loss)]
41    pub fn utilization(&self) -> f64 {
42        if self.limit > 0 {
43            self.used as f64 / self.limit as f64 * 100.0
44        } else {
45            100.0
46        }
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    type TestResult = Result<(), Box<dyn std::error::Error>>;
55
56    #[test]
57    fn test_deserialize_full_graphql_response() -> TestResult {
58        let json = r#"{
59            "data": {
60                "getRequestLimitInfo": {
61                    "limit": 50,
62                    "used": 10,
63                    "resetInSeconds": 3600
64                }
65            }
66        }"#;
67
68        let response: WarpLimitInfoResponse = serde_json::from_str(json)?;
69        let info = &response.data.get_request_limit_info;
70        assert_eq!(info.limit, 50);
71        assert_eq!(info.used, 10);
72        assert_eq!(info.reset_in_seconds, Some(3600));
73        Ok(())
74    }
75
76    #[test]
77    fn test_is_not_limited_when_below_limit() -> TestResult {
78        let json = r#"{
79            "data": {
80                "getRequestLimitInfo": {
81                    "limit": 50,
82                    "used": 10,
83                    "resetInSeconds": 3600
84                }
85            }
86        }"#;
87
88        let response: WarpLimitInfoResponse = serde_json::from_str(json)?;
89        assert!(!response.data.get_request_limit_info.is_limited());
90        Ok(())
91    }
92
93    #[test]
94    fn test_is_limited_when_used_equals_limit() -> TestResult {
95        let json = r#"{
96            "data": {
97                "getRequestLimitInfo": {
98                    "limit": 50,
99                    "used": 50,
100                    "resetInSeconds": null
101                }
102            }
103        }"#;
104
105        let response: WarpLimitInfoResponse = serde_json::from_str(json)?;
106        assert!(response.data.get_request_limit_info.is_limited());
107        Ok(())
108    }
109
110    #[test]
111    fn test_is_limited_when_used_exceeds_limit() -> TestResult {
112        let json = r#"{
113            "data": {
114                "getRequestLimitInfo": {
115                    "limit": 10,
116                    "used": 15,
117                    "resetInSeconds": null
118                }
119            }
120        }"#;
121
122        let response: WarpLimitInfoResponse = serde_json::from_str(json)?;
123        assert!(response.data.get_request_limit_info.is_limited());
124        Ok(())
125    }
126
127    #[test]
128    fn test_utilization_computed_correctly() {
129        let info = WarpRequestLimitInfo {
130            limit: 200,
131            used: 50,
132            reset_in_seconds: None,
133        };
134        assert!((info.utilization() - 25.0).abs() < f64::EPSILON);
135    }
136
137    #[test]
138    fn test_utilization_returns_100_when_zero_limit() {
139        let info = WarpRequestLimitInfo {
140            limit: 0,
141            used: 0,
142            reset_in_seconds: None,
143        };
144        assert!((info.utilization() - 100.0).abs() < f64::EPSILON);
145    }
146
147    #[test]
148    fn test_reset_in_seconds_is_optional() -> TestResult {
149        let json = r#"{
150            "data": {
151                "getRequestLimitInfo": {
152                    "limit": 100,
153                    "used": 0
154                }
155            }
156        }"#;
157
158        let response: WarpLimitInfoResponse = serde_json::from_str(json)?;
159        assert!(
160            response
161                .data
162                .get_request_limit_info
163                .reset_in_seconds
164                .is_none()
165        );
166        Ok(())
167    }
168}