1use std::time::Duration;
11
12use reqwest::{Client, RequestBuilder, Response};
13use tracing::Instrument;
14
15use crate::core::error::{Error, Result};
16use crate::core::secret::ApiToken;
17
18#[derive(Clone, Debug)]
19pub struct HttpClient {
20 http: Client,
21 pub base_url: String,
22 token: ApiToken,
23}
24
25impl HttpClient {
26 pub fn new(base_url: String, token: ApiToken, no_proxy: bool) -> Result<Self> {
30 let mut builder = Client::builder().timeout(Duration::from_secs(30));
31 if no_proxy {
32 builder = builder.no_proxy();
33 }
34 let http = builder.build().map_err(Error::Network)?;
35 Ok(Self {
36 http,
37 base_url,
38 token,
39 })
40 }
41
42 fn url(&self, path: &str) -> String {
43 format!("{}{}", self.base_url, path)
44 }
45
46 pub fn get(&self, path: &str) -> RequestBuilder {
47 self.http.get(self.url(path))
48 }
49
50 pub fn post(&self, path: &str) -> RequestBuilder {
51 self.http.post(self.url(path))
52 }
53
54 pub fn delete(&self, path: &str) -> RequestBuilder {
55 self.http.delete(self.url(path))
56 }
57
58 pub async fn send(
62 &self,
63 method: &'static str,
64 path: &str,
65 builder: RequestBuilder,
66 ) -> Result<Response> {
67 let span =
68 tracing::debug_span!("http.request", method, path, http.status = tracing::field::Empty);
69 async {
70 tracing::debug!("sending request");
71 let resp = builder
72 .bearer_auth(self.token.expose_for_auth())
73 .send()
74 .await
75 .map_err(|e| {
76 tracing::warn!(error = %e, "request failed");
77 Error::Network(e)
78 })?;
79 tracing::Span::current().record("http.status", resp.status().as_u16());
80 tracing::debug!("received response");
81 Ok(resp)
82 }
83 .instrument(span)
84 .await
85 }
86}