wasmtime_wasi_http/
types.rs

1//! Implements the base structure (i.e. [WasiHttpCtx]) that will provide the
2//! implementation of the wasi-http API.
3
4use crate::{
5    bindings::http::types::{self, Method, Scheme},
6    body::{HostIncomingBody, HyperIncomingBody, HyperOutgoingBody},
7};
8use anyhow::bail;
9use bytes::Bytes;
10use http_body_util::BodyExt;
11use hyper::body::Body;
12use hyper::header::HeaderName;
13use std::any::Any;
14use std::time::Duration;
15use wasmtime::component::{Resource, ResourceTable};
16use wasmtime_wasi::p2::Pollable;
17use wasmtime_wasi::runtime::AbortOnDropJoinHandle;
18
19#[cfg(feature = "default-send-request")]
20use {
21    crate::io::TokioIo,
22    crate::{error::dns_error, hyper_request_error},
23    tokio::net::TcpStream,
24    tokio::time::timeout,
25};
26
27/// Capture the state necessary for use in the wasi-http API implementation.
28#[derive(Debug)]
29pub struct WasiHttpCtx {
30    _priv: (),
31}
32
33impl WasiHttpCtx {
34    /// Create a new context.
35    pub fn new() -> Self {
36        Self { _priv: () }
37    }
38}
39
40/// A trait which provides internal WASI HTTP state.
41///
42/// # Example
43///
44/// ```
45/// use wasmtime::component::ResourceTable;
46/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
47/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
48///
49/// struct MyState {
50///     ctx: WasiCtx,
51///     http_ctx: WasiHttpCtx,
52///     table: ResourceTable,
53/// }
54///
55/// impl WasiHttpView for MyState {
56///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
57///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
58/// }
59///
60/// impl WasiView for MyState {
61///     fn ctx(&mut self) -> WasiCtxView<'_> {
62///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
63///     }
64/// }
65///
66/// impl MyState {
67///     fn new() -> MyState {
68///         let mut wasi = WasiCtx::builder();
69///         wasi.arg("./foo.wasm");
70///         wasi.arg("--help");
71///         wasi.env("FOO", "bar");
72///
73///         MyState {
74///             ctx: wasi.build(),
75///             table: ResourceTable::new(),
76///             http_ctx: WasiHttpCtx::new(),
77///         }
78///     }
79/// }
80/// ```
81pub trait WasiHttpView {
82    /// Returns a mutable reference to the WASI HTTP context.
83    fn ctx(&mut self) -> &mut WasiHttpCtx;
84
85    /// Returns the table used to manage resources.
86    fn table(&mut self) -> &mut ResourceTable;
87
88    /// Create a new incoming request resource.
89    fn new_incoming_request<B>(
90        &mut self,
91        scheme: Scheme,
92        req: hyper::Request<B>,
93    ) -> wasmtime::Result<Resource<HostIncomingRequest>>
94    where
95        B: Body<Data = Bytes, Error = hyper::Error> + Send + Sync + 'static,
96        Self: Sized,
97    {
98        let (parts, body) = req.into_parts();
99        let body = body.map_err(crate::hyper_response_error).boxed();
100        let body = HostIncomingBody::new(
101            body,
102            // TODO: this needs to be plumbed through
103            std::time::Duration::from_millis(600 * 1000),
104        );
105        let incoming_req = HostIncomingRequest::new(self, parts, scheme, Some(body))?;
106        Ok(self.table().push(incoming_req)?)
107    }
108
109    /// Create a new outgoing response resource.
110    fn new_response_outparam(
111        &mut self,
112        result: tokio::sync::oneshot::Sender<
113            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
114        >,
115    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
116        let id = self.table().push(HostResponseOutparam { result })?;
117        Ok(id)
118    }
119
120    /// Send an outgoing request.
121    #[cfg(feature = "default-send-request")]
122    fn send_request(
123        &mut self,
124        request: hyper::Request<HyperOutgoingBody>,
125        config: OutgoingRequestConfig,
126    ) -> crate::HttpResult<HostFutureIncomingResponse> {
127        Ok(default_send_request(request, config))
128    }
129
130    /// Send an outgoing request.
131    #[cfg(not(feature = "default-send-request"))]
132    fn send_request(
133        &mut self,
134        request: hyper::Request<HyperOutgoingBody>,
135        config: OutgoingRequestConfig,
136    ) -> crate::HttpResult<HostFutureIncomingResponse>;
137
138    /// Whether a given header should be considered forbidden and not allowed.
139    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
140        DEFAULT_FORBIDDEN_HEADERS.contains(name)
141    }
142
143    /// Number of distinct write calls to the outgoing body's output-stream
144    /// that the implementation will buffer.
145    /// Default: 1.
146    fn outgoing_body_buffer_chunks(&mut self) -> usize {
147        DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS
148    }
149
150    /// Maximum size allowed in a write call to the outgoing body's output-stream.
151    /// Default: 1024 * 1024.
152    fn outgoing_body_chunk_size(&mut self) -> usize {
153        DEFAULT_OUTGOING_BODY_CHUNK_SIZE
154    }
155}
156
157/// The default value configured for [`WasiHttpView::outgoing_body_buffer_chunks`] in [`WasiHttpView`].
158pub const DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS: usize = 1;
159/// The default value configured for [`WasiHttpView::outgoing_body_chunk_size`] in [`WasiHttpView`].
160pub const DEFAULT_OUTGOING_BODY_CHUNK_SIZE: usize = 1024 * 1024;
161
162impl<T: ?Sized + WasiHttpView> WasiHttpView for &mut T {
163    fn ctx(&mut self) -> &mut WasiHttpCtx {
164        T::ctx(self)
165    }
166
167    fn table(&mut self) -> &mut ResourceTable {
168        T::table(self)
169    }
170
171    fn new_response_outparam(
172        &mut self,
173        result: tokio::sync::oneshot::Sender<
174            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
175        >,
176    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
177        T::new_response_outparam(self, result)
178    }
179
180    fn send_request(
181        &mut self,
182        request: hyper::Request<HyperOutgoingBody>,
183        config: OutgoingRequestConfig,
184    ) -> crate::HttpResult<HostFutureIncomingResponse> {
185        T::send_request(self, request, config)
186    }
187
188    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
189        T::is_forbidden_header(self, name)
190    }
191
192    fn outgoing_body_buffer_chunks(&mut self) -> usize {
193        T::outgoing_body_buffer_chunks(self)
194    }
195
196    fn outgoing_body_chunk_size(&mut self) -> usize {
197        T::outgoing_body_chunk_size(self)
198    }
199}
200
201impl<T: ?Sized + WasiHttpView> WasiHttpView for Box<T> {
202    fn ctx(&mut self) -> &mut WasiHttpCtx {
203        T::ctx(self)
204    }
205
206    fn table(&mut self) -> &mut ResourceTable {
207        T::table(self)
208    }
209
210    fn new_response_outparam(
211        &mut self,
212        result: tokio::sync::oneshot::Sender<
213            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
214        >,
215    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
216        T::new_response_outparam(self, result)
217    }
218
219    fn send_request(
220        &mut self,
221        request: hyper::Request<HyperOutgoingBody>,
222        config: OutgoingRequestConfig,
223    ) -> crate::HttpResult<HostFutureIncomingResponse> {
224        T::send_request(self, request, config)
225    }
226
227    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
228        T::is_forbidden_header(self, name)
229    }
230
231    fn outgoing_body_buffer_chunks(&mut self) -> usize {
232        T::outgoing_body_buffer_chunks(self)
233    }
234
235    fn outgoing_body_chunk_size(&mut self) -> usize {
236        T::outgoing_body_chunk_size(self)
237    }
238}
239
240/// A concrete structure that all generated `Host` traits are implemented for.
241///
242/// This type serves as a small newtype wrapper to implement all of the `Host`
243/// traits for `wasi:http`. This type is internally used and is only needed if
244/// you're interacting with `add_to_linker` functions generated by bindings
245/// themselves (or `add_to_linker_get_host`).
246///
247/// This type is automatically used when using
248/// [`add_to_linker_async`](crate::add_to_linker_async)
249/// or
250/// [`add_to_linker_sync`](crate::add_to_linker_sync)
251/// and doesn't need to be manually configured.
252#[repr(transparent)]
253pub struct WasiHttpImpl<T>(pub T);
254
255impl<T: WasiHttpView> WasiHttpView for WasiHttpImpl<T> {
256    fn ctx(&mut self) -> &mut WasiHttpCtx {
257        self.0.ctx()
258    }
259
260    fn table(&mut self) -> &mut ResourceTable {
261        self.0.table()
262    }
263
264    fn new_response_outparam(
265        &mut self,
266        result: tokio::sync::oneshot::Sender<
267            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
268        >,
269    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
270        self.0.new_response_outparam(result)
271    }
272
273    fn send_request(
274        &mut self,
275        request: hyper::Request<HyperOutgoingBody>,
276        config: OutgoingRequestConfig,
277    ) -> crate::HttpResult<HostFutureIncomingResponse> {
278        self.0.send_request(request, config)
279    }
280
281    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
282        self.0.is_forbidden_header(name)
283    }
284
285    fn outgoing_body_buffer_chunks(&mut self) -> usize {
286        self.0.outgoing_body_buffer_chunks()
287    }
288
289    fn outgoing_body_chunk_size(&mut self) -> usize {
290        self.0.outgoing_body_chunk_size()
291    }
292}
293
294/// Set of [http::header::HeaderName], that are forbidden by default
295/// for requests and responses originating in the guest.
296pub const DEFAULT_FORBIDDEN_HEADERS: [http::header::HeaderName; 9] = [
297    hyper::header::CONNECTION,
298    HeaderName::from_static("keep-alive"),
299    hyper::header::PROXY_AUTHENTICATE,
300    hyper::header::PROXY_AUTHORIZATION,
301    HeaderName::from_static("proxy-connection"),
302    hyper::header::TRANSFER_ENCODING,
303    hyper::header::UPGRADE,
304    hyper::header::HOST,
305    HeaderName::from_static("http2-settings"),
306];
307
308/// Removes forbidden headers from a [`hyper::HeaderMap`].
309pub(crate) fn remove_forbidden_headers(
310    view: &mut dyn WasiHttpView,
311    headers: &mut hyper::HeaderMap,
312) {
313    let forbidden_keys = Vec::from_iter(headers.keys().filter_map(|name| {
314        if view.is_forbidden_header(name) {
315            Some(name.clone())
316        } else {
317            None
318        }
319    }));
320
321    for name in forbidden_keys {
322        headers.remove(name);
323    }
324}
325
326/// Configuration for an outgoing request.
327pub struct OutgoingRequestConfig {
328    /// Whether to use TLS for the request.
329    pub use_tls: bool,
330    /// The timeout for connecting.
331    pub connect_timeout: Duration,
332    /// The timeout until the first byte.
333    pub first_byte_timeout: Duration,
334    /// The timeout between chunks of a streaming body
335    pub between_bytes_timeout: Duration,
336}
337
338/// The default implementation of how an outgoing request is sent.
339///
340/// This implementation is used by the `wasi:http/outgoing-handler` interface
341/// default implementation.
342#[cfg(feature = "default-send-request")]
343pub fn default_send_request(
344    request: hyper::Request<HyperOutgoingBody>,
345    config: OutgoingRequestConfig,
346) -> HostFutureIncomingResponse {
347    let handle = wasmtime_wasi::runtime::spawn(async move {
348        Ok(default_send_request_handler(request, config).await)
349    });
350    HostFutureIncomingResponse::pending(handle)
351}
352
353/// The underlying implementation of how an outgoing request is sent. This should likely be spawned
354/// in a task.
355///
356/// This is called from [default_send_request] to actually send the request.
357#[cfg(feature = "default-send-request")]
358pub async fn default_send_request_handler(
359    mut request: hyper::Request<HyperOutgoingBody>,
360    OutgoingRequestConfig {
361        use_tls,
362        connect_timeout,
363        first_byte_timeout,
364        between_bytes_timeout,
365    }: OutgoingRequestConfig,
366) -> Result<IncomingResponse, types::ErrorCode> {
367    let authority = if let Some(authority) = request.uri().authority() {
368        if authority.port().is_some() {
369            authority.to_string()
370        } else {
371            let port = if use_tls { 443 } else { 80 };
372            format!("{}:{port}", authority.to_string())
373        }
374    } else {
375        return Err(types::ErrorCode::HttpRequestUriInvalid);
376    };
377    let tcp_stream = timeout(connect_timeout, TcpStream::connect(&authority))
378        .await
379        .map_err(|_| types::ErrorCode::ConnectionTimeout)?
380        .map_err(|e| match e.kind() {
381            std::io::ErrorKind::AddrNotAvailable => {
382                dns_error("address not available".to_string(), 0)
383            }
384
385            _ => {
386                if e.to_string()
387                    .starts_with("failed to lookup address information")
388                {
389                    dns_error("address not available".to_string(), 0)
390                } else {
391                    types::ErrorCode::ConnectionRefused
392                }
393            }
394        })?;
395
396    let (mut sender, worker) = if use_tls {
397        use rustls::pki_types::ServerName;
398
399        // derived from https://github.com/rustls/rustls/blob/main/examples/src/bin/simpleclient.rs
400        let root_cert_store = rustls::RootCertStore {
401            roots: webpki_roots::TLS_SERVER_ROOTS.into(),
402        };
403        let config = rustls::ClientConfig::builder()
404            .with_root_certificates(root_cert_store)
405            .with_no_client_auth();
406        let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
407        let mut parts = authority.split(":");
408        let host = parts.next().unwrap_or(&authority);
409        let domain = ServerName::try_from(host)
410            .map_err(|e| {
411                tracing::warn!("dns lookup error: {e:?}");
412                dns_error("invalid dns name".to_string(), 0)
413            })?
414            .to_owned();
415        let stream = connector.connect(domain, tcp_stream).await.map_err(|e| {
416            tracing::warn!("tls protocol error: {e:?}");
417            types::ErrorCode::TlsProtocolError
418        })?;
419        let stream = TokioIo::new(stream);
420
421        let (sender, conn) = timeout(
422            connect_timeout,
423            hyper::client::conn::http1::handshake(stream),
424        )
425        .await
426        .map_err(|_| types::ErrorCode::ConnectionTimeout)?
427        .map_err(hyper_request_error)?;
428
429        let worker = wasmtime_wasi::runtime::spawn(async move {
430            match conn.await {
431                Ok(()) => {}
432                // TODO: shouldn't throw away this error and ideally should
433                // surface somewhere.
434                Err(e) => tracing::warn!("dropping error {e}"),
435            }
436        });
437
438        (sender, worker)
439    } else {
440        let tcp_stream = TokioIo::new(tcp_stream);
441        let (sender, conn) = timeout(
442            connect_timeout,
443            // TODO: we should plumb the builder through the http context, and use it here
444            hyper::client::conn::http1::handshake(tcp_stream),
445        )
446        .await
447        .map_err(|_| types::ErrorCode::ConnectionTimeout)?
448        .map_err(hyper_request_error)?;
449
450        let worker = wasmtime_wasi::runtime::spawn(async move {
451            match conn.await {
452                Ok(()) => {}
453                // TODO: same as above, shouldn't throw this error away.
454                Err(e) => tracing::warn!("dropping error {e}"),
455            }
456        });
457
458        (sender, worker)
459    };
460
461    // at this point, the request contains the scheme and the authority, but
462    // the http packet should only include those if addressing a proxy, so
463    // remove them here, since SendRequest::send_request does not do it for us
464    *request.uri_mut() = http::Uri::builder()
465        .path_and_query(
466            request
467                .uri()
468                .path_and_query()
469                .map(|p| p.as_str())
470                .unwrap_or("/"),
471        )
472        .build()
473        .expect("comes from valid request");
474
475    let resp = timeout(first_byte_timeout, sender.send_request(request))
476        .await
477        .map_err(|_| types::ErrorCode::ConnectionReadTimeout)?
478        .map_err(hyper_request_error)?
479        .map(|body| body.map_err(hyper_request_error).boxed());
480
481    Ok(IncomingResponse {
482        resp,
483        worker: Some(worker),
484        between_bytes_timeout,
485    })
486}
487
488impl From<http::Method> for types::Method {
489    fn from(method: http::Method) -> Self {
490        if method == http::Method::GET {
491            types::Method::Get
492        } else if method == hyper::Method::HEAD {
493            types::Method::Head
494        } else if method == hyper::Method::POST {
495            types::Method::Post
496        } else if method == hyper::Method::PUT {
497            types::Method::Put
498        } else if method == hyper::Method::DELETE {
499            types::Method::Delete
500        } else if method == hyper::Method::CONNECT {
501            types::Method::Connect
502        } else if method == hyper::Method::OPTIONS {
503            types::Method::Options
504        } else if method == hyper::Method::TRACE {
505            types::Method::Trace
506        } else if method == hyper::Method::PATCH {
507            types::Method::Patch
508        } else {
509            types::Method::Other(method.to_string())
510        }
511    }
512}
513
514impl TryInto<http::Method> for types::Method {
515    type Error = http::method::InvalidMethod;
516
517    fn try_into(self) -> Result<http::Method, Self::Error> {
518        match self {
519            Method::Get => Ok(http::Method::GET),
520            Method::Head => Ok(http::Method::HEAD),
521            Method::Post => Ok(http::Method::POST),
522            Method::Put => Ok(http::Method::PUT),
523            Method::Delete => Ok(http::Method::DELETE),
524            Method::Connect => Ok(http::Method::CONNECT),
525            Method::Options => Ok(http::Method::OPTIONS),
526            Method::Trace => Ok(http::Method::TRACE),
527            Method::Patch => Ok(http::Method::PATCH),
528            Method::Other(s) => http::Method::from_bytes(s.as_bytes()),
529        }
530    }
531}
532
533/// The concrete type behind a `wasi:http/types/incoming-request` resource.
534#[derive(Debug)]
535pub struct HostIncomingRequest {
536    pub(crate) parts: http::request::Parts,
537    pub(crate) scheme: Scheme,
538    pub(crate) authority: String,
539    /// The body of the incoming request.
540    pub body: Option<HostIncomingBody>,
541}
542
543impl HostIncomingRequest {
544    /// Create a new `HostIncomingRequest`.
545    pub fn new(
546        view: &mut dyn WasiHttpView,
547        mut parts: http::request::Parts,
548        scheme: Scheme,
549        body: Option<HostIncomingBody>,
550    ) -> anyhow::Result<Self> {
551        let authority = match parts.uri.authority() {
552            Some(authority) => authority.to_string(),
553            None => match parts.headers.get(http::header::HOST) {
554                Some(host) => host.to_str()?.to_string(),
555                None => bail!("invalid HTTP request missing authority in URI and host header"),
556            },
557        };
558
559        remove_forbidden_headers(view, &mut parts.headers);
560        Ok(Self {
561            parts,
562            authority,
563            scheme,
564            body,
565        })
566    }
567}
568
569/// The concrete type behind a `wasi:http/types/response-outparam` resource.
570pub struct HostResponseOutparam {
571    /// The sender for sending a response.
572    pub result:
573        tokio::sync::oneshot::Sender<Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>>,
574}
575
576/// The concrete type behind a `wasi:http/types/outgoing-response` resource.
577pub struct HostOutgoingResponse {
578    /// The status of the response.
579    pub status: http::StatusCode,
580    /// The headers of the response.
581    pub headers: FieldMap,
582    /// The body of the response.
583    pub body: Option<HyperOutgoingBody>,
584}
585
586impl TryFrom<HostOutgoingResponse> for hyper::Response<HyperOutgoingBody> {
587    type Error = http::Error;
588
589    fn try_from(
590        resp: HostOutgoingResponse,
591    ) -> Result<hyper::Response<HyperOutgoingBody>, Self::Error> {
592        use http_body_util::Empty;
593
594        let mut builder = hyper::Response::builder().status(resp.status);
595
596        *builder.headers_mut().unwrap() = resp.headers;
597
598        match resp.body {
599            Some(body) => builder.body(body),
600            None => builder.body(
601                Empty::<bytes::Bytes>::new()
602                    .map_err(|_| unreachable!("Infallible error"))
603                    .boxed(),
604            ),
605        }
606    }
607}
608
609/// The concrete type behind a `wasi:http/types/outgoing-request` resource.
610#[derive(Debug)]
611pub struct HostOutgoingRequest {
612    /// The method of the request.
613    pub method: Method,
614    /// The scheme of the request.
615    pub scheme: Option<Scheme>,
616    /// The authority of the request.
617    pub authority: Option<String>,
618    /// The path and query of the request.
619    pub path_with_query: Option<String>,
620    /// The request headers.
621    pub headers: FieldMap,
622    /// The request body.
623    pub body: Option<HyperOutgoingBody>,
624}
625
626/// The concrete type behind a `wasi:http/types/request-options` resource.
627#[derive(Debug, Default)]
628pub struct HostRequestOptions {
629    /// How long to wait for a connection to be established.
630    pub connect_timeout: Option<std::time::Duration>,
631    /// How long to wait for the first byte of the response body.
632    pub first_byte_timeout: Option<std::time::Duration>,
633    /// How long to wait between frames of the response body.
634    pub between_bytes_timeout: Option<std::time::Duration>,
635}
636
637/// The concrete type behind a `wasi:http/types/incoming-response` resource.
638#[derive(Debug)]
639pub struct HostIncomingResponse {
640    /// The response status
641    pub status: u16,
642    /// The response headers
643    pub headers: FieldMap,
644    /// The response body
645    pub body: Option<HostIncomingBody>,
646}
647
648/// The concrete type behind a `wasi:http/types/fields` resource.
649#[derive(Debug)]
650pub enum HostFields {
651    /// A reference to the fields of a parent entry.
652    Ref {
653        /// The parent resource rep.
654        parent: u32,
655
656        /// The function to get the fields from the parent.
657        // NOTE: there's not failure in the result here because we assume that HostFields will
658        // always be registered as a child of the entry with the `parent` id. This ensures that the
659        // entry will always exist while this `HostFields::Ref` entry exists in the table, thus we
660        // don't need to account for failure when fetching the fields ref from the parent.
661        get_fields: for<'a> fn(elem: &'a mut (dyn Any + 'static)) -> &'a mut FieldMap,
662    },
663    /// An owned version of the fields.
664    Owned {
665        /// The fields themselves.
666        fields: FieldMap,
667    },
668}
669
670/// An owned version of `HostFields`
671pub type FieldMap = hyper::HeaderMap;
672
673/// A handle to a future incoming response.
674pub type FutureIncomingResponseHandle =
675    AbortOnDropJoinHandle<anyhow::Result<Result<IncomingResponse, types::ErrorCode>>>;
676
677/// A response that is in the process of being received.
678#[derive(Debug)]
679pub struct IncomingResponse {
680    /// The response itself.
681    pub resp: hyper::Response<HyperIncomingBody>,
682    /// Optional worker task that continues to process the response.
683    pub worker: Option<AbortOnDropJoinHandle<()>>,
684    /// The timeout between chunks of the response.
685    pub between_bytes_timeout: std::time::Duration,
686}
687
688/// The concrete type behind a `wasi:http/types/future-incoming-response` resource.
689#[derive(Debug)]
690pub enum HostFutureIncomingResponse {
691    /// A pending response
692    Pending(FutureIncomingResponseHandle),
693    /// The response is ready.
694    ///
695    /// An outer error will trap while the inner error gets returned to the guest.
696    Ready(anyhow::Result<Result<IncomingResponse, types::ErrorCode>>),
697    /// The response has been consumed.
698    Consumed,
699}
700
701impl HostFutureIncomingResponse {
702    /// Create a new `HostFutureIncomingResponse` that is pending on the provided task handle.
703    pub fn pending(handle: FutureIncomingResponseHandle) -> Self {
704        Self::Pending(handle)
705    }
706
707    /// Create a new `HostFutureIncomingResponse` that is ready.
708    pub fn ready(result: anyhow::Result<Result<IncomingResponse, types::ErrorCode>>) -> Self {
709        Self::Ready(result)
710    }
711
712    /// Returns `true` if the response is ready.
713    pub fn is_ready(&self) -> bool {
714        matches!(self, Self::Ready(_))
715    }
716
717    /// Unwrap the response, panicking if it is not ready.
718    pub fn unwrap_ready(self) -> anyhow::Result<Result<IncomingResponse, types::ErrorCode>> {
719        match self {
720            Self::Ready(res) => res,
721            Self::Pending(_) | Self::Consumed => {
722                panic!("unwrap_ready called on a pending HostFutureIncomingResponse")
723            }
724        }
725    }
726}
727
728#[async_trait::async_trait]
729impl Pollable for HostFutureIncomingResponse {
730    async fn ready(&mut self) {
731        if let Self::Pending(handle) = self {
732            *self = Self::Ready(handle.await);
733        }
734    }
735}