speedrun_api/
client.rs

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/// A client for communicating with the Speedrun.com API
18#[derive(Clone, Debug)]
19pub struct SpeedrunApiClient {
20    client: HttpClient,
21    rest_url: Url,
22    api_key: Auth,
23}
24
25impl SpeedrunApiClient {
26    /// Create a new Speedrun.com API client.
27    pub fn new() -> SpeedrunApiResult<Self> {
28        Self::new_impl::<String>(None)
29    }
30
31    /// Create a new Speedrun.com API client, with the provided API key.
32    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    /// Create a new Speedrun.com API builder.
56    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/// An asynchronous client for communicating with the Speedrun.com API
103#[derive(Clone, Debug)]
104pub struct SpeedrunApiClientAsync {
105    client: AsyncHttpClient,
106    rest_url: Url,
107    api_key: Auth,
108}
109
110impl SpeedrunApiClientAsync {
111    /// Create a new asynchronous Speedrun.com API client
112    pub fn new() -> SpeedrunApiResult<Self> {
113        Self::new_impl::<String>(None)
114    }
115
116    /// Create a new asynchronous Speedrun.com API client, with the provided API
117    /// key.
118    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    /// Create a new Speedrun.com API builder.
143    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/// Speedrun.com API client builder
191#[derive(Debug, Default)]
192pub struct SpeedrunApiBuilder {
193    api_key: Option<String>,
194}
195
196impl SpeedrunApiBuilder {
197    /// Create a new Speedrun.com API client builder.
198    pub fn new() -> Self {
199        SpeedrunApiBuilder::default()
200    }
201
202    /// Add an API key
203    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    /// Build a synchronous Speedrun.com API client.
212    pub fn build(&self) -> SpeedrunApiResult<SpeedrunApiClient> {
213        SpeedrunApiClient::new_impl(self.api_key.as_ref())
214    }
215
216    /// Build an asynchronous Speedrun.com API client.
217    pub fn build_async(&self) -> SpeedrunApiResult<SpeedrunApiClientAsync> {
218        SpeedrunApiClientAsync::new_impl(self.api_key.as_ref())
219    }
220}