1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use std::{fmt, future::Future, pin::Pin};

use anyhow::{anyhow, Result};
use futures_util::TryFutureExt;
use http::{request::Builder, HeaderName, HeaderValue};
use hyper::{body::Bytes, Body, Response};
use tracing::debug;

use super::client::Client;
use super::USER_AGENT;

pub struct RequestBuilder {
    client: Client,
    req_builder: Builder,
}

impl RequestBuilder {
    pub fn new(client: Client, req_builder: Builder) -> Self {
        Self {
            client,
            req_builder,
        }
    }

    pub fn header<K, V>(mut self, key: K, value: V) -> RequestBuilder
    where
        HeaderName: TryFrom<K>,
        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
        HeaderValue: TryFrom<V>,
        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
    {
        self.req_builder = self.req_builder.header(key, value);
        self
    }

    pub async fn send(self) -> Result<Response<Body>> {
        let req = self
            .req_builder
            .header(http::header::USER_AGENT, USER_AGENT)
            .body(hyper::Body::empty())
            .map_err(|err| anyhow!("hyper error: {err:?}"))?;
        debug!(req=?req, "Request");
        self.client
            .hyper
            .request(req)
            .map_err(|err| anyhow!("request error: {err:?}"))
            .await
    }
}

// TODO: prefer static-dispatch once AFIT got stabilized in Rust v1.75
type ResponseExtFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;

pub trait ResponseExt {
    fn bytes(self) -> ResponseExtFuture<Result<Bytes>>;

    #[cfg(feature = "http-client-json")]
    fn json<T: serde::de::DeserializeOwned>(self) -> ResponseExtFuture<Result<T>>
    where
        Self: Sized + Send + 'static,
    {
        let fut = async move {
            let bytes = self.bytes().await?;
            serde_json::from_slice(&bytes).map_err(|err| anyhow!("serialization error: {err:?}"))
        };

        Box::pin(fut)
    }

    fn body_string(self) -> ResponseExtFuture<Result<String>>
    where
        Self: Sized + Send + 'static,
    {
        let fut = async move {
            let body = self.bytes().await?;
            let body_str = std::str::from_utf8(&body)?;
            Ok(body_str.to_string())
        };

        Box::pin(fut)
    }
}

impl<T> ResponseExt for Response<T>
where
    T: hyper::body::HttpBody + Send + 'static,
    T::Data: Send,
    T::Error: fmt::Debug,
{
    fn bytes(self) -> ResponseExtFuture<Result<Bytes>> {
        let fut = async move {
            hyper::body::to_bytes(self.into_body())
                .map_err(|err| anyhow!("{err:?}"))
                .await
        };

        Box::pin(fut)
    }
}