rust_api_kit/http/client/
mod.rs

1pub mod macros;
2mod types;
3use async_trait::async_trait;
4use integration::log_error;
5pub use types::*;
6pub mod integration;
7
8use reqwest::RequestBuilder;
9use serde::{Deserialize, Serialize};
10use url::Url;
11
12pub struct HttpClient {
13    client: reqwest::Client,
14    base_url: Url,
15}
16
17impl HttpClient {
18    pub fn new(base_url: Url) -> Self {
19        let client = reqwest::Client::new();
20
21        Self { client, base_url }
22    }
23}
24
25impl HttpClientTrait for HttpClient {
26    fn get_base_url(&self) -> Url {
27        self.base_url.clone()
28    }
29
30    fn get_client(&self) -> reqwest::Client {
31        self.client.clone()
32    }
33}
34
35impl BasicHttpClientTrait for HttpClient {}
36impl AuthenticatedHttpClientTrait for HttpClient {}
37
38pub trait HttpClientTrait {
39    fn get_base_url(&self) -> Url;
40    fn get_client(&self) -> reqwest::Client;
41}
42
43#[async_trait]
44pub trait AuthenticatedHttpClientTrait
45where
46    Self: HttpClientTrait,
47{
48    async fn request<R, O, E, U, A>(
49        &self,
50        request: R,
51        auth: A,
52    ) -> Result<Result<O, E>, UnexpectedHttpError<U>>
53    where
54        O: for<'de> Deserialize<'de> + Send + 'static,
55        E: for<'de> Deserialize<'de> + Send + std::fmt::Debug + 'static,
56        U: for<'de> Deserialize<'de> + Send + std::fmt::Debug + 'static,
57        R: HttpRequest<O, E, U> + Serialize + AuthenticatedHttpRequest<A> + Send + 'static,
58        A: Auth + Send + 'static,
59    {
60        let base_url = self.get_base_url();
61        let client = self.get_client();
62
63        let mut request_builder = get_request_builder(request, client.clone(), base_url);
64        request_builder = auth.add_auth_to_request(request_builder);
65
66        perform::<R, O, E, U>(client, request_builder).await
67    }
68}
69
70#[async_trait]
71pub trait BasicHttpClientTrait
72where
73    Self: HttpClientTrait,
74{
75    async fn request<R, O, E, U>(&self, request: R) -> Result<Result<O, E>, UnexpectedHttpError<U>>
76    where
77        O: for<'de> Deserialize<'de> + Send + 'static,
78        E: for<'de> Deserialize<'de> + Send + std::fmt::Debug + 'static,
79        U: for<'de> Deserialize<'de> + Send + std::fmt::Debug + 'static,
80        R: HttpRequest<O, E, U> + Serialize + Send + 'static,
81    {
82        let base_url = self.get_base_url();
83        let client = self.get_client();
84        let request_builder = get_request_builder(request, client.clone(), base_url);
85        perform::<R, O, E, U>(client, request_builder).await
86    }
87}
88
89pub trait Auth {
90    fn add_auth_to_request(&self, builder: RequestBuilder) -> RequestBuilder;
91}
92
93impl Auth for BearerToken {
94    fn add_auth_to_request(&self, builder: RequestBuilder) -> RequestBuilder {
95        builder.bearer_auth(self)
96    }
97}
98
99pub trait AuthenticatedHttpRequest<A> {}
100
101pub trait HttpRequest<O, E, U>
102where
103    O: for<'de> Deserialize<'de>,
104    E: for<'de> Deserialize<'de> + std::fmt::Debug,
105    U: for<'de> Deserialize<'de> + std::fmt::Debug,
106    Self: Sized + Serialize,
107{
108    const ENDPOINT: &'static str;
109    const METHOD: RequestMethod;
110
111    fn get_url(base_url: Url) -> Url {
112        base_url.join(Self::ENDPOINT).unwrap()
113    }
114}
115
116fn get_request_builder<R, O, E, U>(
117    request: R,
118    client: reqwest::Client,
119    base_url: Url,
120) -> RequestBuilder
121where
122    O: for<'de> Deserialize<'de>,
123    E: for<'de> Deserialize<'de> + std::fmt::Debug,
124    U: for<'de> Deserialize<'de> + std::fmt::Debug,
125    R: HttpRequest<O, E, U>,
126{
127    let endpoint_url = R::get_url(base_url);
128    let method = R::METHOD;
129
130    let mut request_builder = client.request(method.clone().into(), endpoint_url);
131
132    request_builder = match method {
133        RequestMethod::GET => request_builder.query(&request),
134        _ => request_builder.json(&request),
135    };
136
137    request_builder
138}
139
140async fn perform<R, O, E, U>(
141    client: reqwest::Client,
142    request_builder: RequestBuilder,
143) -> Result<Result<O, E>, UnexpectedHttpError<U>>
144where
145    O: for<'de> Deserialize<'de>,
146    E: for<'de> Deserialize<'de> + std::fmt::Debug,
147    U: for<'de> Deserialize<'de> + std::fmt::Debug,
148    R: HttpRequest<O, E, U>,
149{
150    let request = request_builder.build()?;
151    let response = client.execute(request).await?;
152
153    let body_raw = response.text().await?;
154
155    let body: Response<O, E, U> = serde_json::from_str(body_raw.as_str()).inspect_err(|err| {
156        log_error(format!(
157            "Deserialization error {:?}, {} -> '{}'",
158            err,
159            std::any::type_name::<R>(),
160            body_raw
161        ));
162    })?;
163
164    match body {
165        Response::Ok(ok) => Ok(Ok(ok)),
166        Response::Error(error) => {
167            log_error(format!(
168                "Http error {} -> {:?}",
169                std::any::type_name::<R>(),
170                error
171            ));
172
173            Ok(Err(error))
174        }
175        Response::UnexpectedError(error) => {
176            log_error(format!(
177                "Unexpected http error {} -> {:?}",
178                std::any::type_name::<R>(),
179                error
180            ));
181
182            Err(UnexpectedHttpError::Api(error))
183        }
184    }
185}