1use http::{HeaderMap, HeaderValue, header::AUTHORIZATION};
2use serde::Deserialize;
3
4use crate::{
5 request::{ApiResponse, IntoRequest},
6 scopes::{FactionScope, ForumScope, MarketScope, RacingScope, TornScope, UserScope},
7};
8
9pub trait Executor {
10 type Error: From<serde_json::Error> + From<crate::ApiError> + Send;
11
12 fn execute<R>(
13 &self,
14 request: R,
15 ) -> impl Future<Output = Result<ApiResponse<R::Discriminant>, Self::Error>> + Send
16 where
17 R: IntoRequest;
18
19 fn fetch<R>(&self, request: R) -> impl Future<Output = Result<R::Response, Self::Error>> + Send
20 where
21 R: IntoRequest,
22 {
23 let fut = self.execute(request);
26 async {
27 let resp = fut.await?;
28
29 let bytes = resp.body.unwrap();
30
31 if bytes.starts_with(br#"{"error":{"#) {
32 #[derive(Deserialize)]
33 struct ErrorBody<'a> {
34 code: u16,
35 error: &'a str,
36 }
37 #[derive(Deserialize)]
38 struct ErrorContainer<'a> {
39 #[serde(borrow)]
40 error: ErrorBody<'a>,
41 }
42
43 let error: ErrorContainer = serde_json::from_slice(&bytes)?;
44 return Err(crate::ApiError::new(error.error.code, error.error.error).into());
45 }
46
47 let resp = serde_json::from_slice(&bytes)?;
48
49 Ok(resp)
50 }
51 }
52}
53
54pub struct ReqwestClient(reqwest::Client);
55
56impl ReqwestClient {
57 pub fn new(api_key: &str) -> Self {
58 let mut headers = HeaderMap::with_capacity(1);
59 headers.insert(
60 AUTHORIZATION,
61 HeaderValue::from_str(&format!("ApiKey {api_key}")).unwrap(),
62 );
63
64 let client = reqwest::Client::builder()
65 .default_headers(headers)
66 .brotli(true)
67 .build()
68 .unwrap();
69
70 Self(client)
71 }
72}
73
74pub trait ExecutorExt: Executor + Sized {
75 fn user(&self) -> UserScope<'_, Self>;
76
77 fn faction(&self) -> FactionScope<'_, Self>;
78
79 fn torn(&self) -> TornScope<'_, Self>;
80
81 fn market(&self) -> MarketScope<'_, Self>;
82
83 fn racing(&self) -> RacingScope<'_, Self>;
84
85 fn forum(&self) -> ForumScope<'_, Self>;
86}
87
88impl<T> ExecutorExt for T
89where
90 T: Executor + Sized,
91{
92 fn user(&self) -> UserScope<'_, Self> {
93 UserScope::new(self)
94 }
95
96 fn faction(&self) -> FactionScope<'_, Self> {
97 FactionScope::new(self)
98 }
99
100 fn torn(&self) -> TornScope<'_, Self> {
101 TornScope::new(self)
102 }
103
104 fn market(&self) -> MarketScope<'_, Self> {
105 MarketScope::new(self)
106 }
107
108 fn racing(&self) -> RacingScope<'_, Self> {
109 RacingScope::new(self)
110 }
111
112 fn forum(&self) -> ForumScope<'_, Self> {
113 ForumScope::new(self)
114 }
115}
116
117impl Executor for ReqwestClient {
118 type Error = crate::Error;
119
120 async fn execute<R>(&self, request: R) -> Result<ApiResponse<R::Discriminant>, Self::Error>
121 where
122 R: IntoRequest,
123 {
124 let request = request.into_request();
125 let url = request.url();
126
127 let response = self.0.get(url).send().await?;
128 let status = response.status();
129 let body = response.bytes().await.ok();
130
131 Ok(ApiResponse {
132 discriminant: request.disriminant,
133 status,
134 body,
135 })
136 }
137}
138
139#[cfg(test)]
140mod test {
141 use crate::{ApiError, Error, scopes::test::test_client};
142
143 use super::*;
144
145 #[tokio::test]
146 async fn api_error() {
147 let client = test_client().await;
148
149 let resp = client.faction().basic_for_id((-1).into(), |b| b).await;
150
151 match resp {
152 Err(Error::Api(ApiError::IncorrectIdEntityRelation)) => (),
153 other => panic!("Expected incorrect id entity relation error, got {other:?}"),
154 }
155 }
156}