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