1use std::convert::TryInto;
2use std::fmt::{self, Debug};
3
4use async_trait::async_trait;
5use bytes::Bytes;
6use http::{request, Response as HttpResponse};
7use log::{debug, error};
8use reqwest::blocking::Client;
9use reqwest::Client as AsyncClient;
10use thiserror::Error;
11use url::Url;
12
13use crate::api;
14use crate::auth::Auth;
15
16#[derive(Debug, Error)]
17#[non_exhaustive]
18pub enum RestError {
19 #[error("communication with marketstack: {}", source)]
20 Communication {
21 #[from]
22 source: reqwest::Error,
23 },
24 #[error("`http` error: {}", source)]
25 Http {
26 #[from]
27 source: http::Error,
28 },
29}
30
31#[derive(Debug, Error)]
32#[non_exhaustive]
33pub enum MarketstackError {
34 #[error("failed to parse url: {}", source)]
35 UrlParse {
36 #[from]
37 source: url::ParseError,
38 },
39 #[error("communication with marketstack: {}", source)]
40 Communication {
41 #[from]
42 source: reqwest::Error,
43 },
44 #[error("marketstack HTTP error: {}", status)]
45 Http { status: reqwest::StatusCode },
46 #[error("no response from marketstack")]
47 NoResponse {},
48 #[error("could not parse {} data from JSON: {}", typename, source)]
49 DataType {
50 #[source]
51 source: serde_json::Error,
52 typename: &'static str,
53 },
54 #[error("api error: {}", source)]
55 Api {
56 #[from]
57 source: api::ApiError<RestError>,
58 },
59}
60
61type MarketstackResult<T> = Result<T, MarketstackError>;
62
63#[derive(Clone)]
65pub struct Marketstack {
66 client: Client,
68 rest_url: Url,
70 auth: Auth,
72}
73
74impl Debug for Marketstack {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 f.debug_struct("Marketstack")
77 .field("rest_url", &self.rest_url)
78 .finish()
79 }
80}
81
82impl Marketstack {
83 pub fn new<H, T>(host: H, token: T) -> MarketstackResult<Self>
88 where
89 H: AsRef<str>,
90 T: Into<String>,
91 {
92 Self::new_impl("https", host.as_ref(), Auth::Token(token.into()))
93 }
94
95 pub fn new_insecure<H, T>(host: H, token: T) -> MarketstackResult<Self>
99 where
100 H: AsRef<str>,
101 T: Into<String>,
102 {
103 Self::new_impl("http", host.as_ref(), Auth::Token(token.into()))
104 }
105
106 fn new_impl(protocol: &str, host: &str, auth: Auth) -> MarketstackResult<Self> {
108 let rest_url = Url::parse(&format!("{}://{}/v1/", protocol, host))?;
109
110 let client = Client::builder()
112 .danger_accept_invalid_certs(true)
113 .build()?;
114
115 let api = Marketstack {
116 client,
117 rest_url,
118 auth,
119 };
120
121 api.auth.check_connection(&api)?;
123
124 Ok(api)
125 }
126
127 pub fn builder<H, T>(host: H, token: T) -> MarketstackBuilder
129 where
130 H: Into<String>,
131 T: Into<String>,
132 {
133 MarketstackBuilder::new(host, token)
134 }
135
136 fn rest_simple(
137 &self,
138 request: http::request::Builder,
139 body: Vec<u8>,
140 ) -> Result<HttpResponse<Bytes>, api::ApiError<<Self as api::RestClient>::Error>> {
141 let call = || -> Result<_, RestError> {
142 let http_request = request.body(body)?;
143 let request = http_request.try_into()?;
144 let rsp = self.client.execute(request)?;
145
146 let mut http_rsp = HttpResponse::builder()
147 .status(rsp.status())
148 .version(rsp.version());
149 let headers = http_rsp.headers_mut().unwrap();
150 for (key, value) in rsp.headers() {
151 headers.insert(key, value.clone());
152 }
153 Ok(http_rsp.body(rsp.bytes()?)?)
154 };
155 call().map_err(api::ApiError::client)
156 }
157}
158
159pub struct MarketstackBuilder {
161 protocol: &'static str,
162 host: String,
163 token: Auth,
164}
165
166impl MarketstackBuilder {
167 pub fn new<H, T>(host: H, token: T) -> Self
169 where
170 H: Into<String>,
171 T: Into<String>,
172 {
173 Self {
174 protocol: "https",
175 host: host.into(),
176 token: Auth::Token(token.into()),
177 }
178 }
179
180 pub fn insecure(&mut self) -> &mut Self {
182 self.protocol = "http";
183 self
184 }
185
186 pub fn build(&self) -> MarketstackResult<Marketstack> {
187 Marketstack::new_impl(self.protocol, &self.host, self.token.clone())
188 }
189
190 pub async fn build_async(&self) -> MarketstackResult<AsyncMarketstack> {
191 AsyncMarketstack::new_impl(self.protocol, &self.host, self.token.clone()).await
192 }
193}
194
195impl api::RestClient for Marketstack {
196 type Error = RestError;
197
198 fn rest_endpoint(&self, endpoint: &str) -> Result<Url, api::ApiError<Self::Error>> {
199 Ok(self.rest_url.join(endpoint)?)
200 }
201
202 fn get_auth(&self) -> Option<Auth> {
203 Some(self.auth.clone())
204 }
205}
206
207impl api::Client for Marketstack {
208 fn rest(
209 &self,
210 request: request::Builder,
211 body: Vec<u8>,
212 ) -> Result<HttpResponse<Bytes>, api::ApiError<Self::Error>> {
213 self.rest_simple(request, body)
214 }
215}
216
217#[derive(Clone)]
219pub struct AsyncMarketstack {
220 client: reqwest::Client,
222 rest_url: Url,
224 auth: Auth,
226}
227
228impl Debug for AsyncMarketstack {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 f.debug_struct("AsyncMarketstack")
231 .field("rest_url", &self.rest_url)
232 .finish()
233 }
234}
235
236#[async_trait]
237impl api::RestClient for AsyncMarketstack {
238 type Error = RestError;
239
240 fn rest_endpoint(&self, endpoint: &str) -> Result<Url, api::ApiError<Self::Error>> {
241 debug!(target: "marketstack", "REST api call {}", endpoint);
242 Ok(self.rest_url.join(endpoint)?)
243 }
244
245 fn get_auth(&self) -> Option<Auth> {
246 Some(self.auth.clone())
247 }
248}
249
250#[async_trait]
251impl api::AsyncClient for AsyncMarketstack {
252 async fn rest_async(
253 &self,
254 request: http::request::Builder,
255 body: Vec<u8>,
256 ) -> Result<HttpResponse<Bytes>, api::ApiError<<Self as api::RestClient>::Error>> {
257 self.rest_async_simple(request, body).await
258 }
259}
260
261impl AsyncMarketstack {
262 async fn new_impl(protocol: &str, host: &str, auth: Auth) -> MarketstackResult<Self> {
264 let rest_url = Url::parse(&format!("{}://{}/v1/", protocol, host))?;
265
266 let client = AsyncClient::builder()
267 .danger_accept_invalid_certs(true)
268 .build()?;
269
270 let api = AsyncMarketstack {
271 client,
272 rest_url,
273 auth,
274 };
275
276 api.auth.check_connection_async(&api).await?;
278
279 Ok(api)
280 }
281
282 async fn rest_async_simple(
283 &self,
284 request: http::request::Builder,
285 body: Vec<u8>,
286 ) -> Result<HttpResponse<Bytes>, api::ApiError<<Self as api::RestClient>::Error>> {
287 use futures_util::TryFutureExt;
288 let call = || async {
289 let http_request = request.body(body)?;
290 let request = http_request.try_into()?;
291 let rsp = self.client.execute(request).await?;
292
293 let mut http_rsp = HttpResponse::builder()
294 .status(rsp.status())
295 .version(rsp.version());
296 let headers = http_rsp.headers_mut().unwrap();
297 for (key, value) in rsp.headers() {
298 headers.insert(key, value.clone());
299 }
300 Ok(http_rsp.body(rsp.bytes().await?)?)
301 };
302 call().map_err(api::ApiError::client).await
303 }
304
305 pub async fn new<H, T>(host: H, token: T) -> MarketstackResult<Self>
310 where
311 H: AsRef<str>,
312 T: Into<String>,
313 {
314 Self::new_impl("https", host.as_ref(), Auth::Token(token.into())).await
315 }
316
317 pub async fn new_insecure<H, T>(host: H, token: T) -> MarketstackResult<Self>
321 where
322 H: AsRef<str>,
323 T: Into<String>,
324 {
325 Self::new_impl("http", host.as_ref(), Auth::Token(token.into())).await
326 }
327}