1use crate::error::{Result, ShilpError};
2use reqwest::{Client as HttpClient, Response};
3use serde::de::DeserializeOwned;
4use serde::Serialize;
5use std::collections::HashMap;
6use std::time::Duration;
7
8pub struct Client {
10 pub(crate) base_url: String,
11 pub(crate) http_client: HttpClient,
12 pub(crate) auth_token: Option<String>,
13}
14
15impl Client {
16 pub fn new(base_url: impl Into<String>) -> Self {
18 let http_client = HttpClient::builder()
19 .timeout(Duration::from_secs(30))
20 .build()
21 .expect("Failed to build HTTP client");
22
23 Self {
24 base_url: base_url.into().trim_end_matches('/').to_string(),
25 http_client,
26 auth_token: None,
27 }
28 }
29
30 pub fn with_http_client(base_url: impl Into<String>, http_client: HttpClient) -> Self {
32 Self {
33 base_url: base_url.into().trim_end_matches('/').to_string(),
34 http_client,
35 auth_token: None,
36 }
37 }
38
39 pub fn with_auth(base_url: impl Into<String>, auth_token: impl Into<String>) -> Self {
41 let mut client = Self::new(base_url);
42 client.auth_token = Some(auth_token.into());
43 client
44 }
45
46 pub(crate) async fn do_request<T, B>(
48 &self,
49 method: reqwest::Method,
50 path: &str,
51 body: Option<&B>,
52 query_params: Option<&HashMap<String, String>>,
53 ) -> Result<T>
54 where
55 T: DeserializeOwned,
56 B: Serialize,
57 {
58 let url = format!("{}{}", self.base_url, path);
59 let mut request = self.http_client.request(method, &url);
60
61 if let Some(params) = query_params {
62 request = request.query(params);
63 }
64
65 if let Some(body) = body {
66 request = request.json(body);
67 }
68
69 if let Some(token) = &self.auth_token {
70 request = request.bearer_auth(token);
71 }
72
73 let response = request.send().await?;
74
75 if response.status().is_client_error() || response.status().is_server_error() {
76 let status = response.status().as_u16();
77 let message = response.text().await.unwrap_or_default();
78 return Err(ShilpError::ApiError { message, status });
79 }
80
81 let response_text = response.text().await?;
83 log::debug!("Response Text: {}", response_text);
84 let result = serde_json::from_str::<T>(&response_text)?;
85 Ok(result)
86
87 }
90
91 pub(crate) async fn do_file_request(
93 &self,
94 method: reqwest::Method,
95 path: &str,
96 file_path: &std::path::Path,
97 ) -> Result<()> {
98 let url = format!("{}{}", self.base_url, path);
99
100 let file = tokio::fs::File::open(file_path).await?;
101 let file_name = file_path
102 .file_name()
103 .and_then(|n| n.to_str())
104 .unwrap_or("file");
105
106 let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new());
107 let file_body = reqwest::Body::wrap_stream(stream);
108
109 let form = reqwest::multipart::Form::new().part(
110 "file",
111 reqwest::multipart::Part::stream(file_body).file_name(file_name.to_string()),
112 );
113
114 let response = self.http_client.request(method, &url).multipart(form);
115 let response = if let Some(token) = &self.auth_token {
116 response.bearer_auth(token).send().await?
117 } else {
118 response.send().await?
119 };
120
121 if response.status().is_client_error() || response.status().is_server_error() {
122 let status = response.status().as_u16();
123 let message = response.text().await.unwrap_or_default();
124 return Err(ShilpError::ApiError { message, status });
125 }
126
127 Ok(())
128 }
129
130 pub(crate) async fn do_request_with_file_response(
132 &self,
133 method: reqwest::Method,
134 path: &str,
135 query_params: Option<&HashMap<String, String>>,
136 ) -> Result<Response> {
137 let url = format!("{}{}", self.base_url, path);
138 let mut request = self.http_client.request(method, &url);
139
140 if let Some(params) = query_params {
141 request = request.query(params);
142 }
143
144 if let Some(token) = &self.auth_token {
145 request = request.bearer_auth(token);
146 }
147
148 let response = request.send().await?;
149
150 if response.status().is_client_error() || response.status().is_server_error() {
151 let status = response.status().as_u16();
152 let message = response.text().await.unwrap_or_default();
153 return Err(ShilpError::ApiError { message, status });
154 }
155
156 Ok(response)
157 }
158}