resonite/api_client/
http.rs1use std::num::NonZeroU32;
2
3use governor::{
4 Quota,
5 RateLimiter,
6 clock::DefaultClock,
7 middleware::NoOpMiddleware,
8 state::{InMemoryState, NotKeyed},
9};
10pub use racal::reqwest::ApiClient;
11use reqwest::{
12 Client,
13 RequestBuilder,
14 header::{HeaderMap, HeaderValue},
15};
16
17use super::ApiError;
18use crate::query::{Authenticating, Authentication, NoAuthentication};
19
20type NormalRateLimiter =
21 RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>;
22
23#[must_use]
24fn http_rate_limiter() -> NormalRateLimiter {
25 RateLimiter::direct(
28 Quota::per_minute(NonZeroU32::try_from(12).unwrap())
29 .allow_burst(NonZeroU32::try_from(5).unwrap()),
30 )
31}
32
33pub struct UnauthenticatedResonite {
35 http: Client,
36 rate_limiter: NormalRateLimiter,
37 user_agent: String,
38}
39
40#[async_trait::async_trait]
41impl ApiClient<NoAuthentication> for UnauthenticatedResonite {
42 fn state(&self) -> &NoAuthentication { &NoAuthentication {} }
43
44 fn client(&self) -> &reqwest::Client { &self.http }
45
46 async fn before_request(
47 &self, req: RequestBuilder,
48 ) -> Result<RequestBuilder, racal::reqwest::ApiError> {
49 self.rate_limiter.until_ready().await;
50 Ok(req)
51 }
52}
53
54pub struct AuthenticatingResonite {
59 base: UnauthenticatedResonite,
60 data: Authenticating,
61}
62
63impl From<(UnauthenticatedResonite, Authenticating)>
64 for AuthenticatingResonite
65{
66 fn from(value: (UnauthenticatedResonite, Authenticating)) -> Self {
67 Self { base: value.0, data: value.1 }
68 }
69}
70
71impl From<AuthenticatingResonite> for UnauthenticatedResonite {
72 fn from(value: AuthenticatingResonite) -> Self { value.base }
73}
74
75#[async_trait::async_trait]
76impl ApiClient<Authenticating> for AuthenticatingResonite {
77 fn state(&self) -> &Authenticating { &self.data }
78
79 fn client(&self) -> &reqwest::Client { &self.base.http }
80
81 async fn before_request(
82 &self, mut req: RequestBuilder,
83 ) -> Result<RequestBuilder, racal::reqwest::ApiError> {
84 self.base.rate_limiter.until_ready().await;
85 req = req.header("UID", &self.data.unique_machine_identifier);
86 if let Some(second_factor_token) = &self.data.second_factor {
87 req = req.header("TOTP", second_factor_token);
88 }
89
90 Ok(req)
92 }
93}
94
95pub struct AuthenticatedResonite {
97 auth: Authentication,
98 http: Client,
99 rate_limiter: NormalRateLimiter,
100 user_agent: String,
101}
102
103#[async_trait::async_trait]
104impl ApiClient<Authentication> for AuthenticatedResonite {
105 fn state(&self) -> &Authentication { &self.auth }
106
107 fn client(&self) -> &reqwest::Client { &self.http }
108
109 async fn before_request(
110 &self, req: RequestBuilder,
111 ) -> Result<RequestBuilder, racal::reqwest::ApiError> {
112 self.rate_limiter.until_ready().await;
113 Ok(req)
114 }
115}
116
117impl AuthenticatedResonite {
118 fn http_client(
120 user_agent: &str, auth: &Authentication,
121 ) -> Result<Client, ApiError> {
122 use serde::ser::Error;
123
124 let builder = Client::builder();
125 let mut headers = HeaderMap::new();
126
127 let (header_name, header_value) = auth.to_header();
128
129 headers.insert(
130 header_name,
131 header_value.parse().map_err(|_| {
132 serde_json::Error::custom("Couldn't turn auth into a header")
133 })?,
134 );
135
136 Ok(builder.user_agent(user_agent).default_headers(headers).build()?)
137 }
138
139 pub fn downgrade(self) -> Result<UnauthenticatedResonite, ApiError> {
145 Ok(UnauthenticatedResonite {
146 http: UnauthenticatedResonite::http_client(&self.user_agent)?,
147 rate_limiter: self.rate_limiter,
148 user_agent: self.user_agent,
149 })
150 }
151
152 pub fn new(
158 user_agent: String, auth: impl Into<Authentication> + Send,
159 ) -> Result<Self, ApiError> {
160 let auth = auth.into();
161 Ok(Self {
162 http: Self::http_client(&user_agent, &auth)?,
163 rate_limiter: http_rate_limiter(),
164 user_agent,
165 auth,
166 })
167 }
168}
169
170impl UnauthenticatedResonite {
171 fn http_client(user_agent: &str) -> Result<Client, ApiError> {
173 let mut default_headers = HeaderMap::new();
174 default_headers.insert(
175 reqwest::header::ACCEPT,
176 HeaderValue::from_static("application/json"),
177 );
178 Ok(
179 Client::builder()
180 .user_agent(user_agent)
181 .default_headers(default_headers)
182 .build()?,
183 )
184 }
185
186 pub fn upgrade(
192 self, auth: impl Into<Authentication> + Send,
193 ) -> Result<AuthenticatedResonite, ApiError> {
194 let auth = auth.into();
195 Ok(AuthenticatedResonite {
196 http: AuthenticatedResonite::http_client(&self.user_agent, &auth)?,
197 rate_limiter: self.rate_limiter,
198 user_agent: self.user_agent,
199 auth,
200 })
201 }
202
203 pub fn new(user_agent: String) -> Result<Self, ApiError> {
209 Ok(Self {
210 http: Self::http_client(&user_agent)?,
211 rate_limiter: http_rate_limiter(),
212 user_agent,
213 })
214 }
215}