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}