1use crate::abi;
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10use std::time::Duration;
11
12#[derive(Debug, Clone)]
14pub enum HttpError {
15 RequestFailed(String),
17 Timeout,
19 InvalidOperation,
21 ParseError(String),
23 NetworkError(String),
25}
26
27impl std::fmt::Display for HttpError {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 HttpError::RequestFailed(msg) => write!(f, "Request failed: {}", msg),
31 HttpError::Timeout => write!(f, "Request timed out"),
32 HttpError::InvalidOperation => write!(f, "Invalid operation ID"),
33 HttpError::ParseError(msg) => write!(f, "Parse error: {}", msg),
34 HttpError::NetworkError(msg) => write!(f, "Network error: {}", msg),
35 }
36 }
37}
38
39impl std::error::Error for HttpError {}
40
41#[derive(Debug, Clone)]
43pub struct HttpResponse {
44 pub status: u16,
46 pub body: Vec<u8>,
48}
49
50impl HttpResponse {
51 pub fn is_success(&self) -> bool {
53 self.status >= 200 && self.status < 300
54 }
55
56 pub fn text(&self) -> Result<String, std::string::FromUtf8Error> {
58 String::from_utf8(self.body.clone())
59 }
60
61 pub fn json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
63 serde_json::from_slice(&self.body)
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct RequestBuilder {
70 method: String,
71 url: String,
72 headers: Vec<(String, String)>,
73 body: Vec<u8>,
74 timeout_ms: i32,
75}
76
77impl RequestBuilder {
78 fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
80 Self {
81 method: method.into(),
82 url: url.into(),
83 headers: Vec::new(),
84 body: Vec::new(),
85 timeout_ms: 30000, }
87 }
88
89 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
91 self.headers.push((name.into(), value.into()));
92 self
93 }
94
95 pub fn content_type(self, content_type: impl Into<String>) -> Self {
97 self.header("content-type", content_type)
98 }
99
100 pub fn bearer_auth(self, token: impl Into<String>) -> Self {
102 self.header("authorization", format!("Bearer {}", token.into()))
103 }
104
105 pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
107 self.body = body.into();
108 self
109 }
110
111 pub fn text(self, text: impl Into<String>) -> Self {
113 self.content_type("text/plain")
114 .body(text.into().into_bytes())
115 }
116
117 pub fn json<T: Serialize>(self, value: &T) -> Result<Self, serde_json::Error> {
119 let body = serde_json::to_vec(value)?;
120 Ok(self.content_type("application/json").body(body))
121 }
122
123 pub fn timeout(mut self, duration: Duration) -> Self {
125 self.timeout_ms = duration.as_millis().min(i32::MAX as u128) as i32;
126 self
127 }
128
129 pub fn send(self) -> Result<HttpResponse, HttpError> {
131 let headers_bytes = abi::serialize_pairs(&self.headers);
133
134 let op_id = unsafe {
136 abi::http_fetch_with_options(
137 self.method.as_ptr(),
138 self.method.len() as i32,
139 self.url.as_ptr(),
140 self.url.len() as i32,
141 headers_bytes.as_ptr(),
142 headers_bytes.len() as i32,
143 self.body.as_ptr(),
144 self.body.len() as i32,
145 )
146 };
147
148 if op_id < 0 {
149 return Err(HttpError::RequestFailed("Failed to start HTTP request".into()));
150 }
151
152 let poll_result = unsafe { abi::operation_poll(op_id, self.timeout_ms) };
154
155 match poll_result {
156 1 => {
157 let status = unsafe { abi::operation_result_status(op_id) };
159
160 let body_size = unsafe { abi::operation_result_body(op_id, std::ptr::null_mut(), 0) };
162
163 let body = if body_size > 0 {
164 let mut body_buf = vec![0u8; body_size as usize];
165 unsafe {
166 abi::operation_result_body(
167 op_id,
168 body_buf.as_mut_ptr(),
169 body_buf.len() as i32,
170 );
171 }
172 body_buf
173 } else {
174 Vec::new()
175 };
176
177 unsafe { abi::operation_free(op_id) };
179
180 Ok(HttpResponse {
181 status: status as u16,
182 body,
183 })
184 }
185 0 => {
186 unsafe { abi::operation_free(op_id) };
188 Err(HttpError::Timeout)
189 }
190 -1 => {
191 let body_size = unsafe { abi::operation_result_body(op_id, std::ptr::null_mut(), 0) };
193 let error_msg = if body_size > 0 {
194 let mut buf = vec![0u8; body_size as usize];
195 unsafe {
196 abi::operation_result_body(op_id, buf.as_mut_ptr(), buf.len() as i32);
197 }
198 String::from_utf8_lossy(&buf).into_owned()
199 } else {
200 "Unknown error".into()
201 };
202
203 unsafe { abi::operation_free(op_id) };
204 Err(HttpError::NetworkError(error_msg))
205 }
206 -2 => {
207 Err(HttpError::InvalidOperation)
208 }
209 _ => {
210 unsafe { abi::operation_free(op_id) };
211 Err(HttpError::NetworkError(format!("Unexpected poll result: {}", poll_result)))
212 }
213 }
214 }
215}
216
217#[derive(Debug, Clone, Default)]
228pub struct Client {
229 default_headers: Vec<(String, String)>,
230}
231
232impl Client {
233 pub fn new() -> Self {
235 Self::default()
236 }
237
238 pub fn with_default_headers(headers: Vec<(String, String)>) -> Self {
240 Self {
241 default_headers: headers,
242 }
243 }
244
245 pub fn default_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
247 self.default_headers.push((name.into(), value.into()));
248 self
249 }
250
251 pub fn get(&self, url: impl Into<String>) -> RequestBuilder {
253 self.request("GET", url)
254 }
255
256 pub fn post(&self, url: impl Into<String>) -> RequestBuilder {
258 self.request("POST", url)
259 }
260
261 pub fn put(&self, url: impl Into<String>) -> RequestBuilder {
263 self.request("PUT", url)
264 }
265
266 pub fn delete(&self, url: impl Into<String>) -> RequestBuilder {
268 self.request("DELETE", url)
269 }
270
271 pub fn patch(&self, url: impl Into<String>) -> RequestBuilder {
273 self.request("PATCH", url)
274 }
275
276 pub fn head(&self, url: impl Into<String>) -> RequestBuilder {
278 self.request("HEAD", url)
279 }
280
281 pub fn request(&self, method: impl Into<String>, url: impl Into<String>) -> RequestBuilder {
283 let mut builder = RequestBuilder::new(method, url);
284 for (name, value) in &self.default_headers {
285 builder = builder.header(name.clone(), value.clone());
286 }
287 builder
288 }
289}
290
291pub fn get(url: impl Into<String>) -> Result<HttpResponse, HttpError> {
293 Client::new().get(url).send()
294}
295
296pub fn post_json<T: Serialize>(url: impl Into<String>, body: &T) -> Result<HttpResponse, HttpError> {
298 Client::new()
299 .post(url)
300 .json(body)
301 .map_err(|e| HttpError::ParseError(e.to_string()))?
302 .send()
303}