Skip to main content

mangadex_api/
rate_limit.rs

1use std::{num::ParseIntError, ops::Deref};
2
3use reqwest::{
4    header::{HeaderMap, ToStrError},
5    Response,
6};
7use serde::Serialize;
8use time::OffsetDateTime;
9
10use mangadex_api_types::MangaDexDateTime;
11
12pub const LIMIT: &str = "x-ratelimit-limit";
13
14pub const REMAINING: &str = "x-ratelimit-remaining";
15
16pub const RETRY_AFTER: &str = "x-ratelimit-retry-after";
17
18/// This `RateLimit` struct contains all the data needed for rate limit handling
19/// It can be parsed via a [`reqwest::Response`] or [`reqwest::header::HeaderMap`]
20/// ```rust
21///     use mangadex_api_types_rust::rate_limit::{LIMIT, REMAINING, RETRY_AFTER, RateLimit, RateLimitParseError};
22///     use reqwest::header::{HeaderMap, HeaderValue};
23///
24///     fn main() -> Result<(), RateLimitParseError> {
25///         let mut headers = HeaderMap::new();
26///         headers.append(RETRY_AFTER, HeaderValue::from_static("1698723860"));
27///         headers.append(LIMIT, HeaderValue::from_static("40"));
28///         headers.append(REMAINING, HeaderValue::from_static("39"));
29///         assert_eq!(headers.len(), 3);
30///         let rate_limit: RateLimit = TryFrom::try_from(&headers)?;
31///         assert_eq!(rate_limit.limit, 40);
32///         assert_eq!(rate_limit.remaining, 39);
33///         Ok(())
34///     }
35/// ```
36///
37#[derive(Serialize, Debug, Clone, Default)]
38#[non_exhaustive]
39pub struct RateLimit {
40    /// value from `x-ratelimit-limit` header
41    pub limit: u32,
42    /// value from `x-ratelimit-remaining` header
43    pub remaining: u32,
44    /// value from `x-ratelimit-retry-after` header
45    /// It's normally an [`i64`] [(Unix timestamp)](https://www.unixtimestamp.com/)
46    /// but can be parsed as a [`crate::MangaDexDateTime`]
47    pub retry_after: MangaDexDateTime,
48}
49
50#[derive(Debug, thiserror::Error)]
51#[non_exhaustive]
52pub enum RateLimitParseError {
53    #[error("this header {0} is not found")]
54    HeaderNotFound(String),
55    #[error(transparent)]
56    ToStrError(#[from] ToStrError),
57    #[error(transparent)]
58    ParseIntError(#[from] ParseIntError),
59    #[error(transparent)]
60    ComponnentRangeError(#[from] time::error::ComponentRange),
61}
62
63impl TryFrom<&HeaderMap> for RateLimit {
64    type Error = RateLimitParseError;
65
66    fn try_from(value: &HeaderMap) -> Result<Self, Self::Error> {
67        let limit: u32 = value
68            .iter()
69            .find(|(name, _)| (*name).eq(&LIMIT))
70            .ok_or(RateLimitParseError::HeaderNotFound(LIMIT.to_string()))?
71            .1
72            .to_str()?
73            .parse()?;
74        let remaining: u32 = value
75            .iter()
76            .find(|(name, _)| (*name).eq(&REMAINING))
77            .ok_or(RateLimitParseError::HeaderNotFound(REMAINING.to_string()))?
78            .1
79            .to_str()?
80            .parse()?;
81        let retry_after = MangaDexDateTime::from(OffsetDateTime::from_unix_timestamp(
82            value
83                .iter()
84                .find(|(name, _)| (*name).eq(&RETRY_AFTER))
85                .ok_or(RateLimitParseError::HeaderNotFound(RETRY_AFTER.to_string()))?
86                .1
87                .to_str()?
88                .parse::<i64>()?,
89        )?);
90        Ok(Self {
91            limit,
92            remaining,
93            retry_after,
94        })
95    }
96}
97
98impl TryFrom<&Response> for RateLimit {
99    type Error = RateLimitParseError;
100
101    fn try_from(value: &Response) -> Result<Self, Self::Error> {
102        TryFrom::try_from(value.headers())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::rate_limit::{LIMIT, REMAINING, RETRY_AFTER};
109
110    use super::{RateLimit, RateLimitParseError};
111    use reqwest::header::{HeaderMap, HeaderValue};
112
113    #[test]
114    fn test_ratelimit_parsing() -> Result<(), RateLimitParseError> {
115        let mut headers = HeaderMap::new();
116        headers.append(RETRY_AFTER, HeaderValue::from_static("1698723860"));
117        headers.append(LIMIT, HeaderValue::from_static("40"));
118        headers.append(REMAINING, HeaderValue::from_static("39"));
119        assert_eq!(headers.len(), 3);
120
121        let rate_limit: RateLimit = TryFrom::try_from(&headers)?;
122        assert_eq!(rate_limit.limit, 40);
123        assert_eq!(rate_limit.remaining, 39);
124        Ok(())
125    }
126}
127
128/// This struct is used for rate limited endpoint
129/// `rate_limit` is for the rate limit metadata
130/// `body` is the response data
131#[derive(Debug, Serialize, Clone, Default)]
132#[non_exhaustive]
133pub struct Limited<T> {
134    pub rate_limit: RateLimit,
135    pub body: T,
136}
137
138impl<T> Limited<T> {
139    pub fn drop_body(self) -> Limited<()> {
140        Limited {
141            rate_limit: self.rate_limit,
142            body: (),
143        }
144    }
145}
146
147impl<T> Deref for Limited<T> {
148    type Target = T;
149    fn deref(&self) -> &Self::Target {
150        &self.body
151    }
152}