clientix_core/client/asynchronous/
request.rs1use 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, 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 let mut error = None;
106 match content_type {
107 ContentType::ApplicationJson => {
108 match serde_json::to_string(&body) {
109 Ok(body) => self.config.body = Some(body.into()),
110 Err(_) => error = Some(ClientixError::InvalidRequest("invalid body".to_string()))
111 }
112 }
113 ContentType::ApplicationXWwwFormUrlEncoded => {
114 match serde_urlencoded::to_string(&body) {
115 Ok(body) => {
116 self.config.body = Some(body.into());
117 }
118 Err(_) => error = Some(ClientixError::InvalidRequest("invalid body".to_string())),
119 }
120 },
121 _ => error = Some(ClientixError::InvalidRequest("invalid content type".to_string())),
122 };
123
124 if let Some(error) = error {
125 self.result = Err(error);
126 }
127
128 match content_type.try_into() {
129 Ok(content_type) => {
130 self.config.headers.insert(CONTENT_TYPE, content_type);
131 }
132 Err(_) => {
133 self.result = Err(ClientixError::InvalidRequest("invalid content type".to_string()))
134 }
135 };
136
137 self
138 }
139
140 pub async fn send(self) -> AsyncResponseHandler {
141 if let Err(error) = self.result {
142 return AsyncResponseHandler::new(Err(error));
143 }
144
145 let method_path = strfmt(&self.config.path.path_str, &self.config.path.segments).expect("failed to format path");
146 let full_path = format!("{}{}", self.client.path, method_path);
147 let url = format!("{}{}", self.client.url, full_path);
148
149 if let Ok(client) = self.client.client.lock() {
150 let mut request_builder = match self.method {
151 Method::GET => client.get(url),
152 Method::POST => client.post(url),
153 Method::PUT => client.put(url),
154 Method::DELETE => client.delete(url),
155 _ => todo!(),
156 };
157
158 request_builder = request_builder
159 .headers(self.config.headers)
160 .query(&self.config.queries);
161
162 request_builder = match self.config.body {
163 Some(body) => request_builder.body(body),
164 None => request_builder,
165 };
166
167 request_builder = match self.config.timeout {
168 Some(timeout) => request_builder.timeout(timeout),
169 None => request_builder,
170 };
171
172 return match request_builder.send().await {
173 Ok(response) => AsyncResponseHandler::new(Ok(response)),
174 Err(error) => AsyncResponseHandler::new(Err(ClientixError::Network(error)))
175 }
176 }
177
178 AsyncResponseHandler::new(Err(ClientixError::Other("Client locked".to_string())))
179 }
180
181 fn insert_header(&mut self, key: &str, value: &str, sensitive: bool) {
182 let header_name = if let Ok(name) = HeaderName::from_bytes(key.as_bytes()) {
183 name
184 } else {
185 return;
186 };
187
188 let mut header_value = if let Ok(value) = HeaderValue::from_str(&value) {
189 value
190 } else {
191 return;
192 };
193
194 header_value.set_sensitive(sensitive);
195
196 self.config.headers.insert(header_name, header_value);
197 }
198
199}