1use std::future::Future;
2
3use futures::{Stream, StreamExt};
4#[cfg(feature = "reqwest")]
5use http::{header::AUTHORIZATION, HeaderMap, HeaderValue};
6use serde::Deserialize;
7
8#[cfg(feature = "reqwest")]
9use crate::request::ApiRequest;
10use crate::request::{ApiResponse, IntoRequest};
11#[cfg(feature = "scopes")]
12use crate::scopes::{
13 BulkFactionScope, BulkForumScope, BulkKeyScope, BulkMarketScope, BulkRacingScope,
14 BulkTornScope, BulkUserScope, FactionScope, ForumScope, KeyScope, MarketScope, RacingScope,
15 TornScope, UserScope,
16};
17
18pub trait Executor: Sized {
20 type Error: From<serde_json::Error> + From<crate::ApiError> + Send;
21
22 fn execute<R>(
24 self,
25 request: R,
26 ) -> impl Future<Output = (R::Discriminant, Result<ApiResponse, Self::Error>)> + Send
27 where
28 R: IntoRequest;
29
30 fn fetch<R>(self, request: R) -> impl Future<Output = Result<R::Response, Self::Error>> + Send
32 where
33 R: IntoRequest,
34 {
35 let fut = self.execute(request);
38 async {
39 let resp = fut.await.1?;
40
41 let bytes = resp.body.unwrap();
42
43 if bytes.starts_with(br#"{"error":{"#) {
44 #[derive(Deserialize)]
45 struct ErrorBody<'a> {
46 code: u16,
47 error: &'a str,
48 }
49 #[derive(Deserialize)]
50 struct ErrorContainer<'a> {
51 #[serde(borrow)]
52 error: ErrorBody<'a>,
53 }
54
55 let error: ErrorContainer = serde_json::from_slice(&bytes)?;
56 return Err(crate::ApiError::new(error.error.code, error.error.error).into());
57 }
58
59 let resp = serde_json::from_slice(&bytes)?;
60
61 Ok(resp)
62 }
63 }
64}
65
66pub trait BulkExecutor: Sized {
68 type Error: From<serde_json::Error> + From<crate::ApiError> + Send;
69
70 fn execute<R>(
72 self,
73 requests: impl IntoIterator<Item = R>,
74 ) -> impl Stream<Item = (R::Discriminant, Result<ApiResponse, Self::Error>)> + Unpin
75 where
76 R: IntoRequest;
77
78 fn fetch_many<R>(
80 self,
81 requests: impl IntoIterator<Item = R>,
82 ) -> impl Stream<Item = (R::Discriminant, Result<R::Response, Self::Error>)> + Unpin
83 where
84 R: IntoRequest,
85 {
86 self.execute(requests).map(|(d, r)| {
87 let r = match r {
88 Ok(r) => r,
89 Err(why) => return (d, Err(why)),
90 };
91 let bytes = r.body.unwrap();
92
93 if bytes.starts_with(br#"{"error":{"#) {
94 #[derive(Deserialize)]
95 struct ErrorBody<'a> {
96 code: u16,
97 error: &'a str,
98 }
99 #[derive(Deserialize)]
100 struct ErrorContainer<'a> {
101 #[serde(borrow)]
102 error: ErrorBody<'a>,
103 }
104
105 let error: ErrorContainer = match serde_json::from_slice(&bytes) {
106 Ok(error) => error,
107 Err(why) => return (d, Err(why.into())),
108 };
109 return (
110 d,
111 Err(crate::ApiError::new(error.error.code, error.error.error).into()),
112 );
113 }
114
115 let resp = match serde_json::from_slice(&bytes) {
116 Ok(resp) => resp,
117 Err(why) => return (d, Err(why.into())),
118 };
119
120 (d, Ok(resp))
121 })
122 }
123}
124
125#[cfg(feature = "scopes")]
127pub trait ExecutorExt: Executor + Sized {
128 fn user(self) -> UserScope<Self>;
129
130 fn faction(self) -> FactionScope<Self>;
131
132 fn torn(self) -> TornScope<Self>;
133
134 fn market(self) -> MarketScope<Self>;
135
136 fn racing(self) -> RacingScope<Self>;
137
138 fn forum(self) -> ForumScope<Self>;
139
140 fn key(self) -> KeyScope<Self>;
141}
142
143#[cfg(feature = "scopes")]
144impl<T> ExecutorExt for T
145where
146 T: Executor + Sized,
147{
148 fn user(self) -> UserScope<Self> {
149 UserScope::new(self)
150 }
151
152 fn faction(self) -> FactionScope<Self> {
153 FactionScope::new(self)
154 }
155
156 fn torn(self) -> TornScope<Self> {
157 TornScope::new(self)
158 }
159
160 fn market(self) -> MarketScope<Self> {
161 MarketScope::new(self)
162 }
163
164 fn racing(self) -> RacingScope<Self> {
165 RacingScope::new(self)
166 }
167
168 fn forum(self) -> ForumScope<Self> {
169 ForumScope::new(self)
170 }
171
172 fn key(self) -> KeyScope<Self> {
173 KeyScope::new(self)
174 }
175}
176
177#[cfg(feature = "scopes")]
179pub trait BulkExecutorExt: BulkExecutor + Sized {
180 fn user_bulk(self) -> BulkUserScope<Self>;
181
182 fn faction_bulk(self) -> BulkFactionScope<Self>;
183
184 fn torn_bulk(self) -> BulkTornScope<Self>;
185
186 fn market_bulk(self) -> BulkMarketScope<Self>;
187
188 fn racing_bulk(self) -> BulkRacingScope<Self>;
189
190 fn forum_bulk(self) -> BulkForumScope<Self>;
191
192 fn key_bulk(self) -> BulkKeyScope<Self>;
193}
194
195#[cfg(feature = "scopes")]
196impl<T> BulkExecutorExt for T
197where
198 T: BulkExecutor + Sized,
199{
200 fn user_bulk(self) -> BulkUserScope<Self> {
201 BulkUserScope::new(self)
202 }
203
204 fn faction_bulk(self) -> BulkFactionScope<Self> {
205 BulkFactionScope::new(self)
206 }
207
208 fn torn_bulk(self) -> BulkTornScope<Self> {
209 BulkTornScope::new(self)
210 }
211
212 fn market_bulk(self) -> BulkMarketScope<Self> {
213 BulkMarketScope::new(self)
214 }
215
216 fn racing_bulk(self) -> BulkRacingScope<Self> {
217 BulkRacingScope::new(self)
218 }
219
220 fn forum_bulk(self) -> BulkForumScope<Self> {
221 BulkForumScope::new(self)
222 }
223
224 fn key_bulk(self) -> BulkKeyScope<Self> {
225 BulkKeyScope::new(self)
226 }
227}
228
229#[cfg(feature = "reqwest")]
230pub struct ReqwestClient(reqwest::Client);
232
233#[cfg(feature = "reqwest")]
234impl ReqwestClient {
235 pub fn new(api_key: &str) -> Self {
237 let mut headers = HeaderMap::with_capacity(1);
238 headers.insert(
239 AUTHORIZATION,
240 HeaderValue::from_str(&format!("ApiKey {api_key}")).unwrap(),
241 );
242
243 let client = reqwest::Client::builder()
244 .default_headers(headers)
245 .brotli(true)
246 .build()
247 .unwrap();
248
249 Self(client)
250 }
251}
252
253#[cfg(feature = "reqwest")]
254impl ReqwestClient {
255 async fn execute_api_request(&self, request: ApiRequest) -> Result<ApiResponse, crate::Error> {
256 let url = request.url();
257
258 let response = self.0.get(url).send().await?;
259 let status = response.status();
260 let body = response.bytes().await.ok();
261
262 Ok(ApiResponse { status, body })
263 }
264}
265
266#[cfg(feature = "reqwest")]
267impl Executor for &ReqwestClient {
268 type Error = crate::Error;
269
270 async fn execute<R>(self, request: R) -> (R::Discriminant, Result<ApiResponse, Self::Error>)
271 where
272 R: IntoRequest,
273 {
274 let (d, request) = request.into_request();
275 (d, self.execute_api_request(request).await)
276 }
277}
278
279#[cfg(feature = "reqwest")]
280impl BulkExecutor for &ReqwestClient {
281 type Error = crate::Error;
282
283 fn execute<R>(
284 self,
285 requests: impl IntoIterator<Item = R>,
286 ) -> impl Stream<Item = (R::Discriminant, Result<ApiResponse, Self::Error>)>
287 where
288 R: IntoRequest,
289 {
290 futures::stream::iter(requests)
291 .map(move |r| <Self as Executor>::execute(self, r))
292 .buffer_unordered(25)
293 }
294}
295
296#[cfg(all(test, feature = "reqwest"))]
297mod test {
298 use crate::{scopes::test::test_client, ApiError, Error};
299
300 use super::*;
301
302 #[cfg(feature = "scopes")]
303 #[tokio::test]
304 async fn api_error() {
305 let client = test_client().await;
306
307 let resp = client.faction().basic_for_id((-1).into(), |b| b).await;
308
309 match resp {
310 Err(Error::Api(ApiError::IncorrectIdEntityRelation)) => (),
311 other => panic!("Expected incorrect id entity relation error, got {other:?}"),
312 }
313 }
314
315 #[cfg(feature = "scopes")]
316 #[tokio::test]
317 async fn bulk_request() {
318 let client = test_client().await;
319
320 let stream = client
321 .faction_bulk()
322 .basic_for_id(vec![19.into(), 89.into()], |b| b);
323
324 let mut responses: Vec<_> = stream.collect().await;
325
326 let (_id1, basic1) = responses.pop().unwrap();
327 basic1.unwrap();
328
329 let (_id2, basic2) = responses.pop().unwrap();
330 basic2.unwrap();
331 }
332}