grafbase_sdk/host_io/http.rs
1//! A module for executing HTTP requests.
2
3use std::string::FromUtf8Error;
4
5pub use crate::wit::{HttpError, HttpMethod, HttpVersion};
6pub use http::StatusCode;
7pub use serde_json::Error as JsonDeserializeError;
8pub use url::Url;
9
10use crate::wit::HttpClient;
11use serde::Serialize;
12
13/// Executes a single HTTP request and returns a result containing either an `HttpResponse` or an `HttpError`.
14///
15/// This function delegates the execution of the HTTP request to the underlying `HttpClient`, which handles
16/// the asynchronous sending of the request in the host runtime. From the perspective of the guest, this operation is blocking.
17/// While awaiting the response, other tasks can be executed concurrently by the host thread.
18///
19/// # Arguments
20///
21/// * `request` - A reference to an `HttpRequest` that encapsulates the HTTP method, URL, headers,
22/// body, and optional timeout settings for the request to be sent.
23///
24/// # Returns
25///
26/// This function returns a `Result<HttpResponse, HttpError>`, which represents either the successful response from the server
27/// (`HttpResponse`) or an error that occurred during the execution of the HTTP request (`HttpError`).
28pub fn execute(request: &HttpRequest) -> Result<HttpResponse, HttpError> {
29 HttpClient::execute(&request.0).map(HttpResponse)
30}
31
32/// Executes multiple HTTP requests in a batch and returns their results.
33///
34/// This function takes advantage of `HttpClient::execute_many` to handle multiple requests concurrently
35/// within the host runtime environment. Similar to executing single requests, this operation is blocking from
36/// the guest's point of view but non-blocking on the host side where tasks can run asynchronously.
37///
38/// # Arguments
39///
40/// * `requests` - A `BatchHttpRequest` containing a vector of individual `crate::wit::HttpRequest`
41/// objects. Each represents a complete HTTP request with its own settings and payload data to be sent.
42///
43/// # Returns
44///
45/// It returns a `Vec<Result<HttpResponse, HttpError>>`, which is a vector where each element corresponds
46/// to the result of executing one of the batched requests. Each element will either contain an `HttpResponse`
47/// if the request was successful or an `HttpError` if there was an issue with that particular request.
48pub fn execute_many(requests: BatchHttpRequest) -> Vec<Result<HttpResponse, HttpError>> {
49 HttpClient::execute_many(&requests.requests)
50 .into_iter()
51 .map(|r| r.map(HttpResponse))
52 .collect()
53}
54
55impl From<http::Method> for HttpMethod {
56 fn from(value: http::Method) -> Self {
57 if value == http::Method::GET {
58 Self::Get
59 } else if value == http::Method::POST {
60 Self::Post
61 } else if value == http::Method::PUT {
62 Self::Put
63 } else if value == http::Method::DELETE {
64 Self::Delete
65 } else if value == http::Method::HEAD {
66 Self::Head
67 } else if value == http::Method::OPTIONS {
68 Self::Options
69 } else if value == http::Method::CONNECT {
70 Self::Connect
71 } else if value == http::Method::TRACE {
72 Self::Trace
73 } else if value == http::Method::PATCH {
74 Self::Patch
75 } else {
76 unreachable!()
77 }
78 }
79}
80
81/// A struct that represents an HTTP request.
82#[derive(Debug)]
83pub struct HttpRequest(crate::wit::HttpRequest);
84
85impl HttpRequest {
86 /// Constructs a new `HttpRequestBuilder` for sending a GET request to the specified URL.
87 ///
88 /// # Arguments
89 ///
90 /// * `url` - The URL where the GET request should be sent.
91 ///
92 /// # Returns
93 ///
94 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
95 pub fn get(url: Url) -> HttpRequestBuilder {
96 Self::builder(url, http::Method::GET)
97 }
98
99 /// Constructs a new `HttpRequestBuilder` for sending a POST request to the specified URL.
100 ///
101 /// # Arguments
102 ///
103 /// * `url` - The URL where the POST request should be sent.
104 ///
105 /// # Returns
106 ///
107 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
108 pub fn post(url: Url) -> HttpRequestBuilder {
109 Self::builder(url, http::Method::POST)
110 }
111
112 /// Constructs a new `HttpRequestBuilder` for sending a PUT request to the specified URL.
113 ///
114 /// # Arguments
115 ///
116 /// * `url` - The URL where the PUT request should be sent.
117 ///
118 /// # Returns
119 ///
120 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
121 pub fn put(url: Url) -> HttpRequestBuilder {
122 Self::builder(url, http::Method::PUT)
123 }
124
125 /// Constructs a new `HttpRequestBuilder` for sending a DELETE request to the specified URL.
126 ///
127 /// # Arguments
128 ///
129 /// * `url` - The URL where the DELETE request should be sent.
130 ///
131 /// # Returns
132 ///
133 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
134 pub fn delete(url: Url) -> HttpRequestBuilder {
135 Self::builder(url, http::Method::DELETE)
136 }
137
138 /// Constructs a new `HttpRequestBuilder` for sending a PATCH request to the specified URL.
139 ///
140 /// # Arguments
141 ///
142 /// * `url` - The URL where the PATCH request should be sent.
143 ///
144 /// # Returns
145 ///
146 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
147 pub fn patch(url: Url) -> HttpRequestBuilder {
148 Self::builder(url, http::Method::PATCH)
149 }
150
151 /// Constructs a new `HttpRequestBuilder` for sending a HEAD request to the specified URL.
152 ///
153 /// # Arguments
154 ///
155 /// * `url` - The URL where the HEAD request should be sent.
156 ///
157 /// # Returns
158 ///
159 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
160 pub fn head(url: Url) -> HttpRequestBuilder {
161 Self::builder(url, http::Method::HEAD)
162 }
163
164 /// Constructs a new `HttpRequestBuilder` for sending an OPTIONS request to the specified URL.
165 ///
166 /// # Arguments
167 ///
168 /// * `url` - The URL where the OPTIONS request should be sent.
169 ///
170 /// # Returns
171 ///
172 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
173 pub fn options(url: Url) -> HttpRequestBuilder {
174 Self::builder(url, http::Method::OPTIONS)
175 }
176
177 /// Constructs a new `HttpRequestBuilder` for sending a TRACE request to the specified URL.
178 ///
179 /// # Arguments
180 ///
181 /// * `url` - The URL where the TRACE request should be sent.
182 ///
183 /// # Returns
184 ///
185 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
186 pub fn trace(url: Url) -> HttpRequestBuilder {
187 Self::builder(url, http::Method::TRACE)
188 }
189
190 /// Constructs a new `HttpRequestBuilder` for sending a CONNECT request to the specified URL.
191 ///
192 /// # Arguments
193 ///
194 /// * `url` - The URL where the CONNECT request should be sent.
195 ///
196 /// # Returns
197 ///
198 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
199 pub fn connect(url: Url) -> HttpRequestBuilder {
200 Self::builder(url, http::Method::CONNECT)
201 }
202
203 /// Constructs a new `HttpRequestBuilder` for sending an HTTP request with the specified method and URL.
204 ///
205 /// # Arguments
206 ///
207 /// * `url` - The URL where the request should be sent.
208 /// * `method` - The HTTP method to use for the request (e.g., GET, POST).
209 ///
210 /// # Returns
211 ///
212 /// A builder object (`HttpRequestBuilder`) that can be used to further customize the HTTP request before execution.
213 pub fn builder(url: Url, method: http::Method) -> HttpRequestBuilder {
214 HttpRequestBuilder(crate::wit::HttpRequest {
215 method: method.into(),
216 url: url.to_string(),
217 headers: Default::default(),
218 body: Default::default(),
219 timeout_ms: Default::default(),
220 })
221 }
222}
223
224/// A builder for constructing an `HttpRequest`.
225pub struct HttpRequestBuilder(crate::wit::HttpRequest);
226
227impl HttpRequestBuilder {
228 /// Sets the URI for the HTTP request.
229 ///
230 /// # Arguments
231 ///
232 /// * `uri` - The new URL to which the request will be sent.
233 ///
234 /// # Returns
235 ///
236 /// A mutable reference to self, allowing further chaining of builder methods.
237 pub fn uri(mut self, uri: Url) -> Self {
238 self.0.url = uri.to_string();
239 self
240 }
241
242 /// Adds a header to the HTTP request.
243 ///
244 /// # Arguments
245 ///
246 /// * `name` - The name of the header.
247 /// * `value` - The value of the header.
248 ///
249 /// This method mutably modifies the builder, allowing headers to be added in sequence.
250 pub fn push_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
251 self.0.headers.push((name.into(), value.into()));
252 }
253
254 /// Sets a timeout for the HTTP request in milliseconds.
255 ///
256 /// # Arguments
257 ///
258 /// * `timeout_ms` - The duration of the timeout in milliseconds.
259 ///
260 /// This method mutably modifies the builder, setting an optional timeout for the request.
261 pub fn set_timeout(&mut self, timeout_ms: u64) {
262 self.0.timeout_ms = Some(timeout_ms);
263 }
264
265 /// Sets a JSON body for the HTTP request and adds the appropriate `Content-Type` header.
266 ///
267 /// # Type Parameters
268 ///
269 /// * `T` - A type that implements `Serialize`.
270 ///
271 /// # Arguments
272 ///
273 /// * `body` - The data to be serialized into JSON format and set as the body of the request.
274 ///
275 /// This method constructs a new `HttpRequest` with a JSON payload, returning it for execution.
276 pub fn json<T: Serialize + ?Sized>(mut self, body: &T) -> HttpRequest {
277 self.0
278 .headers
279 .push(("Content-Type".to_string(), "application/json".to_string()));
280
281 self.body(serde_json::to_vec(body).unwrap())
282 }
283
284 /// Sets a form-encoded body for the HTTP request and adds the appropriate `Content-Type` header.
285 ///
286 /// # Type Parameters
287 ///
288 /// * `T` - A type that implements `Serialize`.
289 ///
290 /// # Arguments
291 ///
292 /// * `body` - The data to be serialized into form-urlencoded format and set as the body of the request.
293 ///
294 /// This method constructs a new `HttpRequest` with a URL-encoded payload, returning it for execution.
295 pub fn form<T: Serialize + ?Sized>(mut self, body: &T) -> HttpRequest {
296 self.0.headers.push((
297 "Content-Type".to_string(),
298 "application/x-www-form-urlencoded".to_string(),
299 ));
300
301 self.body(serde_urlencoded::to_string(body).unwrap().into_bytes())
302 }
303
304 /// Sets a raw byte array as the body for the HTTP request.
305 ///
306 /// # Arguments
307 ///
308 /// * `body` - The data to be set as the body of the request in `Vec<u8>` format.
309 ///
310 /// This method constructs and returns a new `HttpRequest` with the specified body.
311 pub fn body(mut self, body: Vec<u8>) -> HttpRequest {
312 self.0.body = body;
313 self.build()
314 }
315
316 /// Constructs a fully configured `HttpRequest` from the builder.
317 pub fn build(self) -> HttpRequest {
318 HttpRequest(self.0)
319 }
320}
321
322/// A structure representing a batch of HTTP requests.
323pub struct BatchHttpRequest {
324 /// A vector holding individual `crate::wit::HttpRequest` objects that are part of this batch.
325 pub(crate) requests: Vec<crate::wit::HttpRequest>,
326}
327
328impl BatchHttpRequest {
329 /// Constructs a new, empty `BatchHttpRequest`.
330 pub fn new() -> Self {
331 Self { requests: Vec::new() }
332 }
333
334 /// Adds a single HTTP request to the batch.
335 pub fn push(&mut self, request: HttpRequest) {
336 self.requests.push(request.0);
337 }
338
339 /// Returns the number of HTTP requests in the batch.
340 pub fn len(&self) -> usize {
341 self.requests.len()
342 }
343
344 /// Determines whether the batch of HTTP requests is empty.
345 #[must_use]
346 pub fn is_empty(&self) -> bool {
347 self.len() == 0
348 }
349}
350
351impl Default for BatchHttpRequest {
352 fn default() -> Self {
353 Self::new()
354 }
355}
356
357/// A struct that represents an HTTP response.
358pub struct HttpResponse(crate::wit::HttpResponse);
359
360impl HttpResponse {
361 /// Returns the status code of the HTTP response.
362 pub fn status(&self) -> http::StatusCode {
363 http::StatusCode::from_u16(self.0.status).expect("must be valid, this comes from reqwest")
364 }
365
366 /// Returns the headers of the HTTP response.
367 pub fn headers(&self) -> &[(String, String)] {
368 &self.0.headers
369 }
370
371 /// Returns the body of the HTTP response.
372 pub fn body(&self) -> &[u8] {
373 &self.0.body
374 }
375
376 /// Attempts to convert the HTTP response body into a UTF-8 encoded `String`.
377 ///
378 /// This method takes ownership of the `HttpResponse` and returns a `Result<String, std::string::FromUtf8Error>`.
379 /// It attempts to interpret the bytes in the body as a valid UTF-8 sequence.
380 pub fn text(self) -> Result<String, FromUtf8Error> {
381 String::from_utf8(self.0.body)
382 }
383
384 /// Attempts to deserialize the HTTP response body as JSON.
385 ///
386 /// This method takes ownership of the `HttpResponse` and returns a `Result<serde_json::Value, serde_json::Error>`.
387 ///
388 /// It attempts to interpret the bytes in the body as valid JSON. The conversion is successful if the
389 /// byte slice represents a valid JSON value according to the JSON specification.
390 pub fn json<'de, T>(&'de self) -> Result<T, JsonDeserializeError>
391 where
392 T: serde::de::Deserialize<'de>,
393 {
394 serde_json::from_slice(&self.0.body)
395 }
396}