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