1use std::time::Duration;
8
9use serde::de::DeserializeOwned;
10
11use crate::error::{DataError, Result};
12
13const TIMEOUT: Duration = Duration::from_secs(15);
15
16const USER_AGENT: &str = concat!("wc-data/", env!("CARGO_PKG_VERSION"));
18
19#[derive(Debug, Clone)]
21pub struct Http {
22 client: reqwest::Client,
23}
24
25impl Http {
26 pub fn new() -> Result<Self> {
31 let client = reqwest::Client::builder()
32 .timeout(TIMEOUT)
33 .user_agent(USER_AGENT)
34 .build()
35 .map_err(|err| DataError::Transport(err.to_string()))?;
36 Ok(Self { client })
37 }
38
39 #[must_use]
41 pub fn with_client(client: reqwest::Client) -> Self {
42 Self { client }
43 }
44
45 pub async fn get_json<T: DeserializeOwned>(
56 &self,
57 url: &str,
58 headers: &[(&str, &str)],
59 ) -> Result<T> {
60 let bytes = self.get_bytes(url, headers).await?;
61 serde_json::from_slice(&bytes).map_err(|err| DataError::Decode(err.to_string()))
62 }
63
64 pub async fn get_bytes(&self, url: &str, headers: &[(&str, &str)]) -> Result<Vec<u8>> {
70 let mut req = self.client.get(url);
71 for (name, value) in headers {
72 req = req.header(*name, *value);
73 }
74 let resp = req
75 .send()
76 .await
77 .map_err(|err| DataError::Transport(err.to_string()))?;
78
79 let status = resp.status();
80 if status.as_u16() == 429 {
81 let retry_after = resp
82 .headers()
83 .get(reqwest::header::RETRY_AFTER)
84 .and_then(|v| v.to_str().ok())
85 .and_then(|v| v.parse::<u64>().ok());
86 return Err(DataError::RateLimited { retry_after });
87 }
88 if !status.is_success() {
89 let message = resp
90 .text()
91 .await
92 .ok()
93 .map(|body| body.chars().take(200).collect::<String>())
94 .filter(|s| !s.is_empty())
95 .unwrap_or_else(|| status.canonical_reason().unwrap_or("error").to_owned());
96 return Err(DataError::Status {
97 status: status.as_u16(),
98 message,
99 });
100 }
101
102 resp.bytes()
103 .await
104 .map(|b| b.to_vec())
105 .map_err(|err| DataError::Transport(err.to_string()))
106 }
107}