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}