1use std::time::Duration;
2
3use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
4
5pub const MAX_RESPONSE_BYTES: usize = 50 * 1024 * 1024; #[derive(Debug, thiserror::Error)]
12pub enum HttpClientError {
13 #[error("invalid header value: {0}")]
14 InvalidHeader(#[from] reqwest::header::InvalidHeaderValue),
15 #[error("failed to build HTTP client: {0}")]
16 Build(#[from] reqwest::Error),
17 #[error("response body exceeded {limit} bytes")]
18 BodyTooLarge { limit: usize },
19 #[error("failed to read response body: {0}")]
20 BodyRead(reqwest::Error),
21}
22
23pub fn default_client() -> Result<reqwest::Client, HttpClientError> {
26 Ok(reqwest::Client::builder()
27 .user_agent(format!("facto/{}", env!("CARGO_PKG_VERSION")))
28 .connect_timeout(Duration::from_secs(5))
29 .timeout(Duration::from_secs(10))
30 .redirect(reqwest::redirect::Policy::none())
31 .https_only(true)
32 .build()?)
33}
34
35pub fn bearer_client(token: &str) -> Result<reqwest::Client, HttpClientError> {
37 let mut headers = HeaderMap::new();
38 headers.insert(
39 AUTHORIZATION,
40 HeaderValue::from_str(&format!("Bearer {}", token))?,
41 );
42 Ok(reqwest::Client::builder()
43 .user_agent(format!("facto/{}", env!("CARGO_PKG_VERSION")))
44 .connect_timeout(Duration::from_secs(5))
45 .timeout(Duration::from_secs(10))
46 .default_headers(headers)
47 .redirect(reqwest::redirect::Policy::none())
48 .https_only(true)
49 .build()?)
50}
51
52pub fn private_token_client(token: &str) -> Result<reqwest::Client, HttpClientError> {
54 let mut headers = HeaderMap::new();
55 headers.insert("PRIVATE-TOKEN", HeaderValue::from_str(token)?);
56 Ok(reqwest::Client::builder()
57 .user_agent(format!("facto/{}", env!("CARGO_PKG_VERSION")))
58 .connect_timeout(Duration::from_secs(5))
59 .timeout(Duration::from_secs(10))
60 .default_headers(headers)
61 .redirect(reqwest::redirect::Policy::none())
62 .https_only(true)
63 .build()?)
64}
65
66pub async fn bytes_with_limit(
73 mut resp: reqwest::Response,
74 limit: usize,
75) -> Result<Vec<u8>, HttpClientError> {
76 let mut buf = Vec::new();
77 while let Some(chunk) = resp.chunk().await.map_err(HttpClientError::BodyRead)? {
78 if buf.len() + chunk.len() > limit {
79 return Err(HttpClientError::BodyTooLarge { limit });
80 }
81 buf.extend_from_slice(&chunk);
82 }
83 Ok(buf)
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[tokio::test]
91 async fn default_client_builds() {
92 default_client().unwrap();
93 }
94
95 #[tokio::test]
96 async fn bearer_client_builds() {
97 bearer_client("ghp_test").unwrap();
98 }
99
100 #[tokio::test]
101 async fn private_token_client_builds() {
102 private_token_client("glpat_test").unwrap();
103 }
104}