Skip to main content

shilp_sdk/
client.rs

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
8/// The main client for interacting with the Shilp API
9pub 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    /// Creates a new Shilp API client with default settings
17    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    /// Creates a new Shilp API client with a custom HTTP client
31    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    /// Creates a new Shilp API client with authentication token
40    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    /// Performs an HTTP request and returns the deserialized response
47    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        // Deserialize the response body to a json map
82        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        // let result = response.json::<T>().await?;
88        // Ok(result)
89    }
90
91    /// Performs a file upload request
92    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    /// Performs a request that returns a file response
131    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}