vectorizer_sdk/
http_transport.rs1use async_trait::async_trait;
4use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
5use reqwest::{Client, ClientBuilder};
6use serde_json::Value;
7
8use crate::error::{Result, VectorizerError};
9use crate::transport::{Protocol, Transport};
10
11pub struct HttpTransport {
13 client: Client,
14 base_url: String,
15}
16
17impl HttpTransport {
18 pub fn new(base_url: &str, api_key: Option<&str>, timeout_secs: u64) -> Result<Self> {
31 let mut headers = HeaderMap::new();
32 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
33
34 if let Some(key) = api_key {
35 let (header_name, header_value) = if looks_like_jwt(key) {
36 ("Authorization", format!("Bearer {key}"))
37 } else {
38 ("X-API-Key", key.to_string())
39 };
40 headers.insert(
41 header_name,
42 HeaderValue::from_str(&header_value).map_err(|e| {
43 VectorizerError::configuration(format!("Invalid auth credential: {e}"))
44 })?,
45 );
46 }
47
48 let client = ClientBuilder::new()
49 .timeout(std::time::Duration::from_secs(timeout_secs))
50 .default_headers(headers)
51 .build()
52 .map_err(|e| {
53 VectorizerError::configuration(format!("Failed to create HTTP client: {e}"))
54 })?;
55
56 Ok(Self {
57 client,
58 base_url: base_url.to_string(),
59 })
60 }
61}
62
63fn looks_like_jwt(token: &str) -> bool {
68 let mut parts = token.split('.');
69 let Some(header) = parts.next() else {
70 return false;
71 };
72 let Some(payload) = parts.next() else {
73 return false;
74 };
75 let Some(signature) = parts.next() else {
76 return false;
77 };
78 if parts.next().is_some() {
79 return false;
80 }
81 !header.is_empty() && !payload.is_empty() && !signature.is_empty()
82}
83
84impl HttpTransport {
85 async fn request(&self, method: &str, path: &str, body: Option<&Value>) -> Result<String> {
87 let url = format!("{}{}", self.base_url, path);
88
89 let mut request = match method {
90 "GET" => self.client.get(&url),
91 "POST" => self.client.post(&url),
92 "PUT" => self.client.put(&url),
93 "DELETE" => self.client.delete(&url),
94 _ => {
95 return Err(VectorizerError::configuration(format!(
96 "Unsupported HTTP method: {method}"
97 )));
98 }
99 };
100
101 if let Some(data) = body {
102 request = request.json(data);
103 }
104
105 let response = request
106 .send()
107 .await
108 .map_err(|e| VectorizerError::network(format!("HTTP request failed: {e}")))?;
109
110 if !response.status().is_success() {
111 let status = response.status();
112 let error_text = response
113 .text()
114 .await
115 .unwrap_or_else(|_| "Unknown error".to_string());
116 return Err(VectorizerError::server(format!(
117 "HTTP {status}: {error_text}"
118 )));
119 }
120
121 response
122 .text()
123 .await
124 .map_err(|e| VectorizerError::network(format!("Failed to read response: {e}")))
125 }
126}
127
128#[async_trait]
129impl Transport for HttpTransport {
130 async fn get(&self, path: &str) -> Result<String> {
131 self.request("GET", path, None).await
132 }
133
134 async fn post(&self, path: &str, data: Option<&Value>) -> Result<String> {
135 self.request("POST", path, data).await
136 }
137
138 async fn put(&self, path: &str, data: Option<&Value>) -> Result<String> {
139 self.request("PUT", path, data).await
140 }
141
142 async fn delete(&self, path: &str) -> Result<String> {
143 self.request("DELETE", path, None).await
144 }
145
146 fn protocol(&self) -> Protocol {
147 Protocol::Http
148 }
149}
150
151impl HttpTransport {
152 pub async fn post_multipart(
154 &self,
155 path: &str,
156 file_bytes: Vec<u8>,
157 filename: &str,
158 form_fields: std::collections::HashMap<String, String>,
159 ) -> Result<String> {
160 let url = format!("{}{}", self.base_url, path);
161
162 let mut form = reqwest::multipart::Form::new();
164
165 let file_part = reqwest::multipart::Part::bytes(file_bytes).file_name(filename.to_string());
167 form = form.part("file", file_part);
168
169 for (key, value) in form_fields {
171 form = form.text(key, value);
172 }
173
174 let response = self
175 .client
176 .post(&url)
177 .multipart(form)
178 .send()
179 .await
180 .map_err(|e| VectorizerError::network(format!("File upload failed: {e}")))?;
181
182 if !response.status().is_success() {
183 let status = response.status();
184 let error_text = response
185 .text()
186 .await
187 .unwrap_or_else(|_| "Unknown error".to_string());
188 return Err(VectorizerError::server(format!(
189 "HTTP {status}: {error_text}"
190 )));
191 }
192
193 response
194 .text()
195 .await
196 .map_err(|e| VectorizerError::network(format!("Failed to read response: {e}")))
197 }
198}