vectorizer_sdk/
http_transport.rs

1//! HTTP transport implementation using reqwest
2
3use 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
11/// HTTP transport client
12pub struct HttpTransport {
13    client: Client,
14    base_url: String,
15}
16
17impl HttpTransport {
18    /// Create a new HTTP transport
19    pub fn new(base_url: &str, api_key: Option<&str>, timeout_secs: u64) -> Result<Self> {
20        let mut headers = HeaderMap::new();
21        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
22
23        if let Some(key) = api_key {
24            headers.insert(
25                "Authorization",
26                HeaderValue::from_str(&format!("Bearer {key}"))
27                    .map_err(|e| VectorizerError::configuration(format!("Invalid API key: {e}")))?,
28            );
29        }
30
31        let client = ClientBuilder::new()
32            .timeout(std::time::Duration::from_secs(timeout_secs))
33            .default_headers(headers)
34            .build()
35            .map_err(|e| {
36                VectorizerError::configuration(format!("Failed to create HTTP client: {e}"))
37            })?;
38
39        Ok(Self {
40            client,
41            base_url: base_url.to_string(),
42        })
43    }
44
45    /// Make a generic request
46    async fn request(&self, method: &str, path: &str, body: Option<&Value>) -> Result<String> {
47        let url = format!("{}{}", self.base_url, path);
48
49        let mut request = match method {
50            "GET" => self.client.get(&url),
51            "POST" => self.client.post(&url),
52            "PUT" => self.client.put(&url),
53            "DELETE" => self.client.delete(&url),
54            _ => {
55                return Err(VectorizerError::configuration(format!(
56                    "Unsupported HTTP method: {method}"
57                )));
58            }
59        };
60
61        if let Some(data) = body {
62            request = request.json(data);
63        }
64
65        let response = request
66            .send()
67            .await
68            .map_err(|e| VectorizerError::network(format!("HTTP request failed: {e}")))?;
69
70        if !response.status().is_success() {
71            let status = response.status();
72            let error_text = response
73                .text()
74                .await
75                .unwrap_or_else(|_| "Unknown error".to_string());
76            return Err(VectorizerError::server(format!(
77                "HTTP {status}: {error_text}"
78            )));
79        }
80
81        response
82            .text()
83            .await
84            .map_err(|e| VectorizerError::network(format!("Failed to read response: {e}")))
85    }
86}
87
88#[async_trait]
89impl Transport for HttpTransport {
90    async fn get(&self, path: &str) -> Result<String> {
91        self.request("GET", path, None).await
92    }
93
94    async fn post(&self, path: &str, data: Option<&Value>) -> Result<String> {
95        self.request("POST", path, data).await
96    }
97
98    async fn put(&self, path: &str, data: Option<&Value>) -> Result<String> {
99        self.request("PUT", path, data).await
100    }
101
102    async fn delete(&self, path: &str) -> Result<String> {
103        self.request("DELETE", path, None).await
104    }
105
106    fn protocol(&self) -> Protocol {
107        Protocol::Http
108    }
109}
110
111impl HttpTransport {
112    /// Upload a file using multipart/form-data (not part of Transport trait)
113    pub async fn post_multipart(
114        &self,
115        path: &str,
116        file_bytes: Vec<u8>,
117        filename: &str,
118        form_fields: std::collections::HashMap<String, String>,
119    ) -> Result<String> {
120        let url = format!("{}{}", self.base_url, path);
121
122        // Create multipart form
123        let mut form = reqwest::multipart::Form::new();
124
125        // Add file
126        let file_part = reqwest::multipart::Part::bytes(file_bytes).file_name(filename.to_string());
127        form = form.part("file", file_part);
128
129        // Add other form fields
130        for (key, value) in form_fields {
131            form = form.text(key, value);
132        }
133
134        let response = self
135            .client
136            .post(&url)
137            .multipart(form)
138            .send()
139            .await
140            .map_err(|e| VectorizerError::network(format!("File upload failed: {e}")))?;
141
142        if !response.status().is_success() {
143            let status = response.status();
144            let error_text = response
145                .text()
146                .await
147                .unwrap_or_else(|_| "Unknown error".to_string());
148            return Err(VectorizerError::server(format!(
149                "HTTP {status}: {error_text}"
150            )));
151        }
152
153        response
154            .text()
155            .await
156            .map_err(|e| VectorizerError::network(format!("Failed to read response: {e}")))
157    }
158}