mangadex_api/
rate_limit.rs1use 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#[derive(Serialize, Debug, Clone, Default)]
38#[non_exhaustive]
39pub struct RateLimit {
40 pub limit: u32,
42 pub remaining: u32,
44 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#[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}