Skip to main content

scraper_trail/
client.rs

1use crate::multi_value::MultiValue;
2use crate::{
3    exchange::{Exchange, Response},
4    request::Request,
5};
6use http::{StatusCode, header::HeaderMap};
7use std::borrow::Cow;
8use std::collections::HashMap;
9
10#[derive(Debug, thiserror::Error)]
11pub enum Error {
12    #[error("HTTP client error")]
13    Http(#[from] reqwest::Error),
14    #[error("Invalid header")]
15    Header(#[from] crate::request::HeaderError),
16    #[error("Header value serialization error")]
17    HeaderValueToStr(#[from] http::header::ToStrError),
18    #[error("Unexpected status")]
19    UnexpectedStatus {
20        status_code: http::StatusCode,
21        body: Option<String>,
22    },
23}
24
25pub async fn json_send<'a>(
26    client: &reqwest::Client,
27    request: Request<'a>,
28) -> Result<crate::exchange::Exchange<'a, serde_json::Value>, Error> {
29    let builder = build_request(client, &request)?;
30    let response = builder.send().await?;
31    let status_code = response.status();
32    let headers = response.headers();
33    let headers = response_headers_to_index_map(headers)?;
34
35    if status_code == StatusCode::OK {
36        let json = response.json().await?;
37
38        Ok(Exchange {
39            request,
40            response: Response {
41                headers,
42                data: json,
43            },
44        })
45    } else {
46        // We attempt to retrieve the body for better error messages, but ignore any failure here.
47        let body = response.text().await.ok();
48
49        Err(Error::UnexpectedStatus { status_code, body })
50    }
51}
52
53pub async fn text_send<'a>(
54    client: &reqwest::Client,
55    request: Request<'a>,
56) -> Result<crate::exchange::Exchange<'a, String>, Error> {
57    let builder = build_request(client, &request)?;
58    let response = builder.send().await?;
59    let status_code = response.status();
60    let headers = response.headers();
61    let headers = response_headers_to_index_map(headers)?;
62
63    if status_code == StatusCode::OK {
64        let text = response.text().await?;
65
66        Ok(Exchange {
67            request,
68            response: Response {
69                headers,
70                data: text,
71            },
72        })
73    } else {
74        // We attempt to retrieve the body for better error messages, but ignore any failure here.
75        let body = response.text().await.ok();
76
77        Err(Error::UnexpectedStatus { status_code, body })
78    }
79}
80
81fn build_request<'a>(
82    client: &reqwest::Client,
83    request: &'a Request<'a>,
84) -> Result<reqwest::RequestBuilder, crate::request::HeaderError> {
85    let builder = client
86        .request(request.method.clone(), request.url.clone())
87        .headers(request.header_map()?);
88
89    Ok(if let Some(body) = request.body.as_ref() {
90        builder.body(body.to_string())
91    } else {
92        builder
93    })
94}
95
96fn response_headers_to_index_map(
97    response_headers: &HeaderMap,
98) -> Result<HashMap<Cow<'static, str>, MultiValue<'static>>, http::header::ToStrError> {
99    let mut result: HashMap<Cow<'static, str>, MultiValue<'static>> = HashMap::new();
100
101    for (name, value) in response_headers {
102        let value = value.to_str()?;
103
104        match result.entry(name.as_str().to_string().into()) {
105            std::collections::hash_map::Entry::Occupied(mut entry) => {
106                let multi_value = entry.get_mut();
107                multi_value.push(value.to_string());
108            }
109            std::collections::hash_map::Entry::Vacant(entry) => {
110                entry.insert(MultiValue::new(value.to_string()));
111            }
112        }
113    }
114
115    Ok(result)
116}