resonite/api_client/
http.rs

1use 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	// ~5 seconds per request sustained over one minute, allowing up to a request
26	// per second in bursts.
27	RateLimiter::direct(
28		Quota::per_minute(NonZeroU32::try_from(12).unwrap())
29			.allow_burst(NonZeroU32::try_from(5).unwrap()),
30	)
31}
32
33/// The main API client without authentication
34pub 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
54/// The main API client that's in the process of authentication
55///
56/// Created with a tuple of the unauthenticated client & authentication,
57/// and can always be downgraded into an unauthenticated client.
58pub 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(dbg!(req))
91		Ok(req)
92	}
93}
94
95/// The main API client with authentication
96pub 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	/// Creates an API client
119	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	/// Removes authentication to the API client
140	///
141	/// # Errors
142	///
143	/// If deserializing user agent fails.
144	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	/// Creates a new authenticated Resonite API client
153	///
154	/// # Errors
155	///
156	/// If deserializing user agent into a header fails
157	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	/// Creates an unauthenticated API client
172	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	/// Adds authentication to the API client
187	///
188	/// # Errors
189	///
190	/// If deserializing user agent or authentication fails.
191	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	/// Creates a new Resonite API client
204	///
205	/// # Errors
206	///
207	/// If deserializing user agent into a header fails
208	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}