rust_api_kit/http/client/
mod.rs1pub 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}