Skip to main content

typesafe_rs/
transport.rs

1use std::time::Duration;
2
3use bytes::Bytes;
4use http::{HeaderMap, Method, StatusCode};
5use url::Url;
6
7use crate::error::{Error, TransportError};
8
9#[derive(Debug)]
10pub(crate) struct HttpTransport {
11    client: reqwest::Client,
12}
13
14pub(crate) struct RawResponse {
15    pub status: StatusCode,
16    pub headers: HeaderMap,
17    pub body: Bytes,
18    pub attempts: u32,
19}
20
21impl HttpTransport {
22    pub(crate) fn new() -> Result<Self, Error> {
23        let client = reqwest::Client::builder()
24            .tcp_nodelay(true)
25            .pool_idle_timeout(Duration::from_secs(90))
26            .http2_keep_alive_interval(Duration::from_secs(30))
27            .http2_keep_alive_timeout(Duration::from_secs(10))
28            .http2_keep_alive_while_idle(true)
29            .http2_adaptive_window(true)
30            .build()
31            .map_err(|err| Error::InvalidRequest(format!("failed to build HTTP client: {err}")))?;
32        Ok(Self { client })
33    }
34
35    pub(crate) async fn send(
36        &self,
37        method: Method,
38        url: Url,
39        headers: HeaderMap,
40        body: Option<Bytes>,
41        timeout: Duration,
42    ) -> Result<RawResponse, Error> {
43        let mut request = self
44            .client
45            .request(method, url)
46            .headers(headers)
47            .timeout(timeout);
48        if let Some(body) = body {
49            request = request.body(body);
50        }
51        let response = match request.send().await {
52            Ok(response) => response,
53            Err(err) => return Err(classify_reqwest(err, timeout)),
54        };
55        let status = response.status();
56        let headers = response.headers().clone();
57        let body = match response.bytes().await {
58            Ok(body) => body,
59            Err(err) => return Err(classify_reqwest(err, timeout)),
60        };
61        Ok(RawResponse {
62            status,
63            headers,
64            body,
65            attempts: 1,
66        })
67    }
68}
69
70fn classify_reqwest(err: reqwest::Error, timeout: Duration) -> Error {
71    if err.is_timeout() {
72        Error::Timeout { after: timeout }
73    } else {
74        Error::Connection(TransportError::from_reqwest(&err))
75    }
76}
77
78pub(crate) fn join_endpoint(base: &Url, path: &str) -> Result<Url, Error> {
79    let mut root = base.as_str().trim_end_matches('/').to_owned();
80    root.push_str(path);
81    Url::parse(&root).map_err(|err| Error::InvalidRequest(format!("invalid endpoint URL: {err}")))
82}