cargo_api/client/
reqwest.rs

1use crate::api::ApiError::HttpRequest;
2use crate::api::{ApiError, Client};
3use bytes::Bytes;
4use http::request::Builder;
5use reqwest::ResponseBuilderExt;
6use std::borrow::Cow;
7use url::Url;
8
9const CRATES_API: &'static str = "https://crates.io/api/";
10
11pub struct ReqwestClient {
12    client: reqwest::blocking::Client,
13    // Doesn't include the version and beyond.
14    // A full path would be: https://crates.io/api/v1/crates/cargo-api
15    url: Url,
16}
17
18impl ReqwestClient {
19    pub fn new(user_agent: &str) -> Self {
20        Self {
21            client: reqwest::blocking::ClientBuilder::default()
22                .user_agent(user_agent)
23                .build()
24                .unwrap(),
25            url: Url::parse(CRATES_API).unwrap(),
26        }
27    }
28
29    fn send_request(
30        &self,
31        request: http::Request<Vec<u8>>,
32    ) -> Result<http::Response<Bytes>, Error> {
33        // https://docs.rs/reqwest/latest/reqwest/blocking/struct.Request.html#method.try_from
34        let req = request.try_into()?;
35
36        // execute the query
37        let res = self.client.execute(req)?;
38
39        // no try_into from reqwest::blocking::Response for http::Response
40        let http_res = into_http_response(res)?;
41
42        Ok(http_res)
43    }
44}
45
46impl Client for ReqwestClient {
47    type Error = Error;
48
49    fn base_endpoint(&self, path: &str) -> Result<Url, ApiError<Self::Error>> {
50        self.url.join(path).map_err(|e| ApiError::Url {
51            error: e,
52            url: Cow::Borrowed(CRATES_API),
53            path: Cow::Owned(path.to_string()),
54        })
55    }
56
57    fn send(
58        &self,
59        request_builder: Builder,
60        body: Vec<u8>,
61    ) -> Result<http::Response<Bytes>, ApiError<Self::Error>> {
62        let request = request_builder
63            .body(body)
64            .map_err(|error| HttpRequest { error })?;
65
66        self.send_request(request)
67            .map_err(|error| ApiError::Client { error })
68    }
69}
70
71#[derive(Debug, thiserror::Error)]
72pub enum Error {
73    #[error(transparent)]
74    Reqwest {
75        #[from]
76        error: reqwest::Error,
77    },
78}
79
80fn into_http_response(res: reqwest::blocking::Response) -> Result<http::Response<Bytes>, Error> {
81    let mut builder = http::response::Builder::new()
82        .status(res.status())
83        .version(res.version())
84        .url(res.url().clone());
85
86    for (name, value) in res.headers() {
87        builder = builder.header(name, value);
88    }
89
90    let body = res.bytes()?;
91    let response = builder.body(body).unwrap();
92
93    Ok(response)
94}