1use crate::error::{Error, Result};
4use crate::common;
5use bytes::Bytes;
6use http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode};
7use std::time::Duration;
8use tokio::io::{AsyncReadExt, AsyncWriteExt};
9use tokio::net::TcpStream;
10
11#[derive(Clone)]
13pub struct Client {
14 pub config: ClientConfig,
16}
17
18#[derive(Clone)]
20pub struct ClientConfig {
21 pub timeout: Duration,
23 pub default_headers: HeaderMap,
25 pub avl_auth: Option<String>,
27 pub region: Option<String>,
29 pub compression: bool,
31 pub max_redirects: usize,
33}
34
35impl Default for ClientConfig {
36 fn default() -> Self {
37 Self {
38 timeout: common::DEFAULT_TIMEOUT,
39 default_headers: HeaderMap::new(),
40 avl_auth: None,
41 region: None,
42 compression: false,
43 max_redirects: 5,
44 }
45 }
46}
47
48impl Client {
49 pub fn new() -> Self {
51 Self {
52 config: ClientConfig::default(),
53 }
54 }
55
56 pub fn builder() -> ClientBuilder {
58 ClientBuilder::new()
59 }
60
61 pub fn get(&self, url: impl Into<String>) -> RequestBuilder {
63 self.request(Method::GET, url)
64 }
65
66 pub fn post(&self, url: impl Into<String>) -> RequestBuilder {
68 self.request(Method::POST, url)
69 }
70
71 pub fn put(&self, url: impl Into<String>) -> RequestBuilder {
73 self.request(Method::PUT, url)
74 }
75
76 pub fn delete(&self, url: impl Into<String>) -> RequestBuilder {
78 self.request(Method::DELETE, url)
79 }
80
81 pub fn patch(&self, url: impl Into<String>) -> RequestBuilder {
83 self.request(Method::PATCH, url)
84 }
85
86 pub fn head(&self, url: impl Into<String>) -> RequestBuilder {
88 self.request(Method::HEAD, url)
89 }
90
91 pub fn request(&self, method: Method, url: impl Into<String>) -> RequestBuilder {
93 RequestBuilder {
94 client: self.clone(),
95 method,
96 url: url.into(),
97 headers: self.config.default_headers.clone(),
98 body: None,
99 query_params: Vec::new(),
100 timeout: Some(self.config.timeout),
101 }
102 }
103}
104
105impl Default for Client {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111pub struct ClientBuilder {
113 config: ClientConfig,
114}
115
116impl ClientBuilder {
117 pub fn new() -> Self {
119 Self {
120 config: ClientConfig::default(),
121 }
122 }
123
124 pub fn timeout(mut self, timeout: Duration) -> Self {
126 self.config.timeout = timeout;
127 self
128 }
129
130 pub fn default_header(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Result<Self> {
132 let name = HeaderName::from_bytes(name.as_ref().as_bytes())
133 .map_err(|_| Error::InvalidHeader {
134 name: name.as_ref().to_string(),
135 value: value.as_ref().to_string(),
136 })?;
137 let value = HeaderValue::from_str(value.as_ref())
138 .map_err(|_| Error::InvalidHeader {
139 name: name.to_string(),
140 value: value.as_ref().to_string(),
141 })?;
142 self.config.default_headers.insert(name, value);
143 Ok(self)
144 }
145
146 pub fn avl_auth(mut self, token: impl Into<String>) -> Self {
148 self.config.avl_auth = Some(token.into());
149 self
150 }
151
152 pub fn region(mut self, region: impl Into<String>) -> Self {
154 self.config.region = Some(region.into());
155 self
156 }
157
158 pub fn compression(mut self, enabled: bool) -> Self {
160 self.config.compression = enabled;
161 self
162 }
163
164 pub fn max_redirects(mut self, max: usize) -> Self {
166 self.config.max_redirects = max;
167 self
168 }
169
170 pub fn build(self) -> Result<Client> {
172 Ok(Client { config: self.config })
173 }
174}
175
176impl Default for ClientBuilder {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182pub struct RequestBuilder {
184 client: Client,
185 pub method: Method,
187 url: String,
188 headers: HeaderMap,
189 body: Option<Bytes>,
190 query_params: Vec<(String, String)>,
191 timeout: Option<Duration>,
192}
193
194impl RequestBuilder {
195 pub fn header(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Result<Self> {
197 let name = HeaderName::from_bytes(name.as_ref().as_bytes())
198 .map_err(|_| Error::InvalidHeader {
199 name: name.as_ref().to_string(),
200 value: value.as_ref().to_string(),
201 })?;
202 let value = HeaderValue::from_str(value.as_ref())
203 .map_err(|_| Error::InvalidHeader {
204 name: name.to_string(),
205 value: value.as_ref().to_string(),
206 })?;
207 self.headers.insert(name, value);
208 Ok(self)
209 }
210
211 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
213 self.body = Some(body.into());
214 self
215 }
216
217 pub fn json<T: serde::Serialize>(mut self, json: &T) -> Result<Self> {
219 let json_str = serde_json::to_string(json)
220 .map_err(|e| Error::JsonError { source: e.to_string() })?;
221 self.body = Some(Bytes::from(json_str));
222 self = self.header("Content-Type", "application/json")?;
223 Ok(self)
224 }
225
226 pub fn query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
228 self.query_params.push((key.into(), value.into()));
229 self
230 }
231
232 pub fn timeout(mut self, timeout: Duration) -> Self {
234 self.timeout = Some(timeout);
235 self
236 }
237
238 pub async fn send(self) -> Result<Response> {
240 let mut full_url = self.url.clone();
242 if !self.query_params.is_empty() {
243 let query_string = self.query_params
244 .iter()
245 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
246 .collect::<Vec<_>>()
247 .join("&");
248 full_url = format!("{}?{}", full_url, query_string);
249 }
250
251 let (host, port, _is_https) = common::parse_url(&full_url)?;
253
254 let addr = format!("{}:{}", host, port);
256 let mut stream = tokio::time::timeout(
257 self.timeout.unwrap_or(common::DEFAULT_TIMEOUT),
258 TcpStream::connect(&addr)
259 )
260 .await
261 .map_err(|_| Error::Timeout { duration: self.timeout.unwrap_or(common::DEFAULT_TIMEOUT) })?
262 .map_err(|e| Error::ConnectionFailed { addr: addr.clone(), source: e })?;
263
264 let path = full_url
266 .find("://")
267 .and_then(|pos| full_url[pos + 3..].find('/'))
268 .map(|pos| &full_url[full_url.find("://").unwrap() + 3 + pos..])
269 .unwrap_or("/");
270
271 let mut request = format!("{} {} HTTP/1.1\r\n", self.method, path);
272 request.push_str(&format!("Host: {}\r\n", host));
273 request.push_str("Connection: close\r\n");
274
275 for (name, value) in self.headers.iter() {
277 request.push_str(&format!("{}: {}\r\n", name, value.to_str().unwrap_or("")));
278 }
279
280 if let Some(auth) = &self.client.config.avl_auth {
282 request.push_str(&format!("Authorization: Bearer {}\r\n", auth));
283 }
284
285 if let Some(body) = &self.body {
287 request.push_str(&format!("Content-Length: {}\r\n", body.len()));
288 request.push_str("\r\n");
289 } else {
290 request.push_str("\r\n");
291 }
292
293 stream.write_all(request.as_bytes()).await?;
295 if let Some(body) = &self.body {
296 stream.write_all(body).await?;
297 }
298
299 let mut response_data = Vec::new();
301 stream.read_to_end(&mut response_data).await?;
302
303 parse_response(response_data)
305 }
306}
307
308pub struct Response {
310 status: StatusCode,
311 headers: HeaderMap,
312 body: Bytes,
313}
314
315impl Response {
316 pub fn status(&self) -> StatusCode {
318 self.status
319 }
320
321 pub fn headers(&self) -> &HeaderMap {
323 &self.headers
324 }
325
326 pub fn bytes(&self) -> &Bytes {
328 &self.body
329 }
330
331 pub async fn text(self) -> Result<String> {
333 String::from_utf8(self.body.to_vec())
334 .map_err(|e| Error::Internal { message: e.to_string() })
335 }
336
337 pub async fn json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
339 serde_json::from_slice(&self.body)
340 .map_err(|e| Error::JsonError { source: e.to_string() })
341 }
342
343 pub fn is_success(&self) -> bool {
345 self.status.is_success()
346 }
347}
348
349fn parse_response(data: Vec<u8>) -> Result<Response> {
350 let separator = b"\r\n\r\n";
352 let mut header_end = 0;
353
354 for i in 0..data.len().saturating_sub(3) {
355 if &data[i..i + 4] == separator {
356 header_end = i + 4;
357 break;
358 }
359 }
360
361 if header_end == 0 {
362 return Err(Error::Internal {
363 message: "Invalid HTTP response: no header/body separator found".to_string(),
364 });
365 }
366
367 let header_data = &data[..header_end - 4];
368 let header_str = String::from_utf8_lossy(header_data);
369 let mut lines = header_str.lines();
370
371 let status_line = lines.next().ok_or_else(|| Error::Internal {
373 message: "Empty response".to_string(),
374 })?;
375
376 let status_code = status_line
377 .split_whitespace()
378 .nth(1)
379 .and_then(|s| s.parse::<u16>().ok())
380 .ok_or_else(|| Error::Internal {
381 message: format!("Invalid status line: {}", status_line),
382 })?;
383
384 let status = StatusCode::from_u16(status_code)
385 .map_err(|_| Error::Internal {
386 message: format!("Invalid status code: {}", status_code),
387 })?;
388
389 let mut headers = HeaderMap::new();
391
392 for line in lines {
393 if line.is_empty() {
394 break;
395 }
396
397 if let Some(pos) = line.find(':') {
398 let name = &line[..pos].trim();
399 let value = &line[pos + 1..].trim();
400
401 if let (Ok(name), Ok(value)) = (
402 HeaderName::from_bytes(name.as_bytes()),
403 HeaderValue::from_str(value),
404 ) {
405 headers.insert(name, value);
406 }
407 }
408 }
409
410 let body = if header_end < data.len() {
412 Bytes::copy_from_slice(&data[header_end..])
413 } else {
414 Bytes::new()
415 };
416
417 Ok(Response {
418 status,
419 headers,
420 body,
421 })
422}
423
424pub type Request = RequestBuilder;
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn test_client_builder() {
433 let client = Client::builder()
434 .timeout(Duration::from_secs(10))
435 .avl_auth("test-token")
436 .region("br-saopaulo-1")
437 .compression(true)
438 .build()
439 .unwrap();
440
441 assert_eq!(client.config.timeout, Duration::from_secs(10));
442 assert_eq!(client.config.avl_auth, Some("test-token".to_string()));
443 assert_eq!(client.config.region, Some("br-saopaulo-1".to_string()));
444 assert!(client.config.compression);
445 }
446
447 #[test]
448 fn test_request_builder_methods() {
449 let client = Client::new();
450
451 let get_req = client.get("https://example.com");
452 assert_eq!(get_req.method, Method::GET);
453
454 let post_req = client.post("https://example.com");
455 assert_eq!(post_req.method, Method::POST);
456 }
457
458 #[test]
459 fn test_request_with_query_params() {
460 let client = Client::new();
461 let req = client
462 .get("https://api.example.com/data")
463 .query("limit", "100")
464 .query("offset", "0");
465
466 assert_eq!(req.query_params.len(), 2);
467 assert_eq!(req.query_params[0], ("limit".to_string(), "100".to_string()));
468 }
469
470 #[tokio::test]
471 async fn test_parse_response() {
472 let response_data = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nHello";
473 let response = parse_response(response_data.to_vec()).unwrap();
474
475 assert_eq!(response.status(), StatusCode::OK);
476 assert!(response.is_success());
477 assert_eq!(response.body.len(), 5);
478 }
479}