grafbase_sdk/host_io/
http.rs

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