1use std::convert::TryInto;
2
3use async_trait::async_trait;
4use futures::TryFutureExt;
5use log::debug;
6use reqwest::{blocking::Client as HttpClient, Client as AsyncHttpClient};
7use url::Url;
8
9use crate::{
10 api,
11 auth::Auth,
12 error::{RestError, SpeedrunApiResult},
13};
14
15const SPEEDRUN_API_BASE_URL: &str = "https://www.speedrun.com/api/v1/";
16
17#[derive(Clone, Debug)]
19pub struct SpeedrunApiClient {
20 client: HttpClient,
21 rest_url: Url,
22 api_key: Auth,
23}
24
25impl SpeedrunApiClient {
26 pub fn new() -> SpeedrunApiResult<Self> {
28 Self::new_impl::<String>(None)
29 }
30
31 pub fn with_api_key<S>(api_key: S) -> SpeedrunApiResult<Self>
33 where
34 S: Into<String>,
35 {
36 Self::new_impl(Some(api_key))
37 }
38
39 fn new_impl<S>(api_key: Option<S>) -> SpeedrunApiResult<Self>
40 where
41 S: Into<String>,
42 {
43 let rest_url = Url::parse(SPEEDRUN_API_BASE_URL)?;
44 let api_key = Auth {
45 token: api_key.map(Into::into),
46 };
47
48 Ok(SpeedrunApiClient {
49 client: HttpClient::new(),
50 rest_url,
51 api_key,
52 })
53 }
54
55 pub fn builder() -> SpeedrunApiBuilder {
57 SpeedrunApiBuilder::new()
58 }
59}
60
61impl api::RestClient for SpeedrunApiClient {
62 type Error = RestError;
63
64 fn rest_endpoint(&self, endpoint: &str) -> Result<Url, api::ApiError<Self::Error>> {
65 debug!("REST api call {}", endpoint);
66 self.rest_url
67 .join(endpoint.trim_start_matches('/'))
68 .map_err(From::from)
69 }
70
71 fn has_api_key(&self) -> bool {
72 self.api_key.token.is_some()
73 }
74}
75
76impl api::Client for SpeedrunApiClient {
77 fn rest(
78 &self,
79 mut request: http::request::Builder,
80 body: Vec<u8>,
81 ) -> Result<http::Response<bytes::Bytes>, api::ApiError<Self::Error>> {
82 let call = || -> Result<_, RestError> {
83 self.api_key
84 .set_auth_header(request.headers_mut().unwrap())?;
85 let http_request = request.body(body)?;
86 let request = http_request.try_into()?;
87 let rsp = self.client.execute(request)?;
88
89 let mut http_rsp = http::Response::builder()
90 .status(rsp.status())
91 .version(rsp.version());
92 let headers = http_rsp.headers_mut().unwrap();
93 for (key, val) in rsp.headers() {
94 headers.insert(key, val.clone());
95 }
96 http_rsp.body(rsp.bytes()?).map_err(From::from)
97 };
98 call().map_err(api::ApiError::client)
99 }
100}
101
102#[derive(Clone, Debug)]
104pub struct SpeedrunApiClientAsync {
105 client: AsyncHttpClient,
106 rest_url: Url,
107 api_key: Auth,
108}
109
110impl SpeedrunApiClientAsync {
111 pub fn new() -> SpeedrunApiResult<Self> {
113 Self::new_impl::<String>(None)
114 }
115
116 pub fn with_api_key<S>(api_key: S) -> SpeedrunApiResult<Self>
119 where
120 S: Into<String>,
121 {
122 Self::new_impl(Some(api_key.into()))
123 }
124
125 fn new_impl<S>(api_key: Option<S>) -> SpeedrunApiResult<Self>
126 where
127 S: Into<String>,
128 {
129 let rest_url = Url::parse(SPEEDRUN_API_BASE_URL)?;
130 let client = AsyncHttpClient::new();
131 let auth = Auth {
132 token: api_key.map(Into::into),
133 };
134 let api = Self {
135 client,
136 rest_url,
137 api_key: auth,
138 };
139 Ok(api)
140 }
141
142 pub fn builder() -> SpeedrunApiBuilder {
144 SpeedrunApiBuilder::new()
145 }
146}
147
148impl api::RestClient for SpeedrunApiClientAsync {
149 type Error = RestError;
150
151 fn rest_endpoint(&self, endpoint: &str) -> Result<Url, api::ApiError<Self::Error>> {
152 debug!("REST api call {}", endpoint);
153 self.rest_url
154 .join(endpoint.trim_start_matches('/'))
155 .map_err(From::from)
156 }
157
158 fn has_api_key(&self) -> bool {
159 self.api_key.token.is_some()
160 }
161}
162
163#[async_trait]
164impl api::AsyncClient for SpeedrunApiClientAsync {
165 async fn rest_async(
166 &self,
167 mut request: http::request::Builder,
168 body: Vec<u8>,
169 ) -> Result<http::Response<bytes::Bytes>, api::ApiError<Self::Error>> {
170 let call = || async {
171 self.api_key
172 .set_auth_header(request.headers_mut().unwrap())?;
173 let http_request = request.body(body)?;
174 let request = http_request.try_into()?;
175 let rsp = self.client.execute(request).await?;
176
177 let mut http_rsp = http::Response::builder()
178 .status(rsp.status())
179 .version(rsp.version());
180 let headers = http_rsp.headers_mut().unwrap();
181 for (key, val) in rsp.headers() {
182 headers.insert(key, val.clone());
183 }
184 http_rsp.body(rsp.bytes().await?).map_err(From::from)
185 };
186 call().map_err(api::ApiError::client).await
187 }
188}
189
190#[derive(Debug, Default)]
192pub struct SpeedrunApiBuilder {
193 api_key: Option<String>,
194}
195
196impl SpeedrunApiBuilder {
197 pub fn new() -> Self {
199 SpeedrunApiBuilder::default()
200 }
201
202 pub fn api_key<S>(&mut self, value: S) -> &mut Self
204 where
205 S: Into<String>,
206 {
207 self.api_key = Some(value.into());
208 self
209 }
210
211 pub fn build(&self) -> SpeedrunApiResult<SpeedrunApiClient> {
213 SpeedrunApiClient::new_impl(self.api_key.as_ref())
214 }
215
216 pub fn build_async(&self) -> SpeedrunApiResult<SpeedrunApiClientAsync> {
218 SpeedrunApiClientAsync::new_impl(self.api_key.as_ref())
219 }
220}