clientix_core/client/asynchronous/
request.rs

1use std::collections::HashMap;
2use std::time::Duration;
3use base64::Engine;
4use base64::prelude::BASE64_STANDARD;
5use http::{HeaderMap, HeaderName, HeaderValue, Method};
6use http::header::{AUTHORIZATION, CONTENT_TYPE};
7use reqwest::Body;
8use serde::Serialize;
9use strfmt::strfmt;
10use crate::client::asynchronous::client::AsyncClient;
11use crate::client::asynchronous::response::AsyncResponseHandler;
12use crate::client::RequestPath;
13use crate::client::result::{ClientixError, ClientixErrorData, ClientixResult};
14use crate::core::headers::content_type::ContentType;
15
16pub struct AsyncRequestConfig {
17    path: RequestPath,
18    headers: HeaderMap,
19    queries: Vec<(String, String)>,
20    body: Option<Body>,
21    timeout: Option<Duration>
22}
23
24pub struct AsyncRequestBuilder {
25    client: AsyncClient,
26    method: Method,
27    config: AsyncRequestConfig,
28    result: ClientixResult<()>
29}
30
31impl AsyncRequestBuilder {
32
33    pub fn builder(client: AsyncClient, method: Method) -> AsyncRequestBuilder {
34        AsyncRequestBuilder {
35            client,
36            method,
37            config: AsyncRequestConfig {
38                path: RequestPath {
39                    path_str: Default::default(),
40                    segments: Default::default()
41                },
42                headers: Default::default(),
43                queries: Default::default(),
44                body: Default::default(),
45                timeout: None
46            },
47            result: Ok(())
48        }
49    }
50
51    pub fn path(mut self, path: &str) -> Self {
52        self.config.path.path_str = path.to_string();
53
54        self
55    }
56
57    pub fn path_segment(mut self, id: &str, value: &str) -> Self {
58        self.config.path.segments.insert(id.to_string(), value.to_string());
59
60        self
61    }
62
63    pub fn query(mut self, key: &str, value: &str) -> Self {
64        self.config.queries.push((key.to_string(), value.to_string()));
65
66        self
67    }
68
69    pub fn queries(mut self, queries: HashMap<String, String>) -> Self {
70        for (key, value) in queries {
71            self.config.queries.push((key, value));
72        }
73
74        self
75    }
76
77    pub fn header(mut self, key: &str, value: &str) -> Self {
78        self.insert_header(key, value, false);
79
80        self
81    }
82
83    pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
84        for (key, value) in headers {
85            self.insert_header(key.as_str(), value.as_str(), false);
86        }
87
88        self
89    }
90
91    pub fn basic_auth(mut self, username: &str, password: &str) -> Self {
92        let basic_token = format!("Basic {}", BASE64_STANDARD.encode(format!("{username}:{password}")));
93        self.insert_header(AUTHORIZATION.as_str(), basic_token.as_str(), true);
94
95        self
96    }
97
98    pub fn bearer_auth(mut self, token: &str) -> Self {
99        self.insert_header(AUTHORIZATION.as_str(), format!("Bearer {}", token).as_str(), true);
100
101        self
102    }
103
104    pub fn body<T: Serialize>(mut self, body: T, content_type: ContentType) -> Self {
105        match content_type {
106            ContentType::ApplicationJson => {
107                match serde_json::to_string(&body) {
108                    Ok(body) => self.config.body = Some(body.into()),
109                    Err(err) => self.result = Err(ClientixError::IO(ClientixErrorData::new(), Some(err.into())))
110                }
111            }
112            ContentType::ApplicationXWwwFormUrlEncoded => {
113                match serde_urlencoded::to_string(&body) {
114                    Ok(body) => self.config.body = Some(body.into()),
115                    Err(err) => self.result = Err(ClientixError::IO(ClientixErrorData::new(), Some(err.into()))),
116                }
117            },
118            _ => {
119                let error_data = ClientixErrorData::builder().message(format!("invalid content type: {:?}", content_type).as_str()).build();
120                self.result = Err(ClientixError::InvalidRequest(error_data, None));
121            }
122        };
123
124        match content_type.try_into() {
125            Ok(content_type) => {
126                self.config.headers.insert(CONTENT_TYPE, content_type);
127            },
128            Err(err) => {
129                let error_data = ClientixErrorData::builder().message(format!("invalid content type: {:?}. {:?}", content_type, err).as_str()).build();
130                self.result = Err(ClientixError::InvalidRequest(error_data, None));
131            }
132        };
133
134        self
135    }
136
137    pub async fn send(self) -> AsyncResponseHandler {
138        if let Err(error) = self.result {
139            return AsyncResponseHandler::new(Err(error));
140        }
141
142        let method_path = strfmt(&self.config.path.path_str, &self.config.path.segments).expect("failed to format path");
143        let full_path = format!("{}{}", self.client.path, method_path);
144        let url = format!("{}{}", self.client.url, full_path);
145
146        match self.client.client.lock() {
147            Ok(client) => {
148                let mut request_builder = match self.method {
149                    Method::GET => client.get(url),
150                    Method::POST => client.post(url),
151                    Method::PUT => client.put(url),
152                    Method::DELETE => client.delete(url),
153                    Method::HEAD => client.head(url),
154                    Method::PATCH => client.patch(url),
155                    _ => {
156                        let error_data = ClientixErrorData::builder().message(format!("invalid method: {:?}", self.method).as_str()).build();
157                        return AsyncResponseHandler::new(Err(ClientixError::InvalidRequest(error_data, None)));
158                    },
159                };
160
161                request_builder = request_builder
162                    .headers(self.config.headers)
163                    .query(&self.config.queries);
164
165                request_builder = match self.config.body {
166                    Some(body) => request_builder.body(body),
167                    None => request_builder,
168                };
169
170                request_builder = match self.config.timeout {
171                    Some(timeout) => request_builder.timeout(timeout),
172                    None => request_builder,
173                };
174
175                match request_builder.send().await {
176                    Ok(response) => AsyncResponseHandler::new(Ok(response)),
177                    Err(error) => AsyncResponseHandler::new(Err(ClientixError::Http(ClientixErrorData::new(), Some(error.into()))))
178                }
179            },
180            Err(err) => {
181                let error_data = ClientixErrorData::builder().message(format!("client locked: {:?}", err).as_str()).build();
182                AsyncResponseHandler::new(Err(ClientixError::Other(error_data, None)))
183            }
184        }
185    }
186
187    fn insert_header(&mut self, key: &str, value: &str, sensitive: bool) {
188        let header_name = if let Ok(name) = HeaderName::from_bytes(key.as_bytes()) {
189            name
190        } else {
191            return;
192        };
193
194        let mut header_value = if let Ok(value) = HeaderValue::from_str(&value) {
195            value
196        } else {
197            return;
198        };
199
200        header_value.set_sensitive(sensitive);
201
202        self.config.headers.insert(header_name, header_value);
203    }
204
205}