wrpc_interface_http/
lib.rs

1pub mod bindings {
2    wit_bindgen_wrpc::generate!({
3        world: "interfaces",
4        generate_all,
5    });
6}
7
8#[cfg(feature = "http")]
9pub fn try_fields_to_header_map(
10    fields: bindings::wrpc::http::types::Fields,
11) -> anyhow::Result<http::HeaderMap> {
12    use anyhow::Context as _;
13
14    let mut headers = http::HeaderMap::new();
15    for (name, values) in fields {
16        let name: http::HeaderName = name.parse().context("failed to parse header name")?;
17        let http::header::Entry::Vacant(entry) = headers.entry(name) else {
18            anyhow::bail!("duplicate header entry");
19        };
20        let Some((first, values)) = values.split_first() else {
21            continue;
22        };
23        let first = first
24            .as_ref()
25            .try_into()
26            .context("failed to construct header value")?;
27        let mut entry = entry.insert_entry(first);
28        for value in values {
29            let value = value
30                .as_ref()
31                .try_into()
32                .context("failed to construct header value")?;
33            entry.append(value);
34        }
35    }
36    Ok(headers)
37}
38
39#[cfg(feature = "http")]
40pub fn try_header_map_to_fields(
41    headers: http::HeaderMap,
42) -> anyhow::Result<bindings::wrpc::http::types::Fields> {
43    use anyhow::Context as _;
44
45    let headers_len = headers.keys_len();
46    headers
47        .into_iter()
48        .try_fold(
49            Vec::with_capacity(headers_len),
50            |mut headers, (name, value)| {
51                if let Some(name) = name {
52                    headers.push((name.to_string(), vec![value.as_bytes().to_vec().into()]));
53                } else {
54                    let (_, ref mut values) = headers
55                        .last_mut()
56                        .context("header name missing and fields are empty")?;
57                    values.push(value.as_bytes().to_vec().into());
58                }
59                anyhow::Ok(headers)
60            },
61        )
62        .context("failed to construct fields")
63}
64
65#[cfg(feature = "http")]
66impl From<&http::Method> for bindings::wrpc::http::types::Method {
67    fn from(method: &http::Method) -> Self {
68        match method.as_str() {
69            "GET" => Self::Get,
70            "HEAD" => Self::Head,
71            "POST" => Self::Post,
72            "PUT" => Self::Put,
73            "DELETE" => Self::Delete,
74            "CONNECT" => Self::Connect,
75            "OPTIONS" => Self::Options,
76            "TRACE" => Self::Trace,
77            "PATCH" => Self::Patch,
78            _ => Self::Other(method.to_string()),
79        }
80    }
81}
82
83#[cfg(feature = "http")]
84impl TryFrom<&bindings::wrpc::http::types::Method> for http::method::Method {
85    type Error = http::method::InvalidMethod;
86
87    fn try_from(method: &bindings::wrpc::http::types::Method) -> Result<Self, Self::Error> {
88        use bindings::wrpc::http::types::Method;
89
90        match method {
91            Method::Get => Ok(Self::GET),
92            Method::Head => Ok(Self::HEAD),
93            Method::Post => Ok(Self::POST),
94            Method::Put => Ok(Self::PUT),
95            Method::Delete => Ok(Self::DELETE),
96            Method::Connect => Ok(Self::CONNECT),
97            Method::Options => Ok(Self::OPTIONS),
98            Method::Trace => Ok(Self::TRACE),
99            Method::Patch => Ok(Self::PATCH),
100            Method::Other(method) => method.parse(),
101        }
102    }
103}
104
105#[cfg(feature = "wasmtime-wasi-http")]
106impl From<wasmtime_wasi_http::bindings::http::types::Method>
107    for bindings::wrpc::http::types::Method
108{
109    fn from(method: wasmtime_wasi_http::bindings::http::types::Method) -> Self {
110        match method {
111            wasmtime_wasi_http::bindings::http::types::Method::Get => Self::Get,
112            wasmtime_wasi_http::bindings::http::types::Method::Head => Self::Head,
113            wasmtime_wasi_http::bindings::http::types::Method::Post => Self::Post,
114            wasmtime_wasi_http::bindings::http::types::Method::Put => Self::Put,
115            wasmtime_wasi_http::bindings::http::types::Method::Delete => Self::Delete,
116            wasmtime_wasi_http::bindings::http::types::Method::Connect => Self::Connect,
117            wasmtime_wasi_http::bindings::http::types::Method::Options => Self::Options,
118            wasmtime_wasi_http::bindings::http::types::Method::Trace => Self::Trace,
119            wasmtime_wasi_http::bindings::http::types::Method::Patch => Self::Patch,
120            wasmtime_wasi_http::bindings::http::types::Method::Other(other) => Self::Other(other),
121        }
122    }
123}
124
125#[cfg(feature = "wasmtime-wasi-http")]
126impl From<bindings::wrpc::http::types::Method>
127    for wasmtime_wasi_http::bindings::http::types::Method
128{
129    fn from(method: bindings::wrpc::http::types::Method) -> Self {
130        use bindings::wrpc::http::types::Method;
131
132        match method {
133            Method::Get => Self::Get,
134            Method::Head => Self::Head,
135            Method::Post => Self::Post,
136            Method::Put => Self::Put,
137            Method::Delete => Self::Delete,
138            Method::Connect => Self::Connect,
139            Method::Options => Self::Options,
140            Method::Trace => Self::Trace,
141            Method::Patch => Self::Patch,
142            Method::Other(other) => Self::Other(other),
143        }
144    }
145}
146
147#[cfg(feature = "http")]
148impl From<&http::uri::Scheme> for bindings::wrpc::http::types::Scheme {
149    fn from(scheme: &http::uri::Scheme) -> Self {
150        match scheme.as_str() {
151            "http" => Self::Http,
152            "https" => Self::Https,
153            _ => Self::Other(scheme.to_string()),
154        }
155    }
156}
157
158#[cfg(feature = "http")]
159impl TryFrom<&bindings::wrpc::http::types::Scheme> for http::uri::Scheme {
160    type Error = http::uri::InvalidUri;
161
162    fn try_from(scheme: &bindings::wrpc::http::types::Scheme) -> Result<Self, Self::Error> {
163        use bindings::wrpc::http::types::Scheme;
164
165        match scheme {
166            Scheme::Http => Ok(Self::HTTP),
167            Scheme::Https => Ok(Self::HTTPS),
168            Scheme::Other(scheme) => scheme.parse(),
169        }
170    }
171}
172
173#[cfg(feature = "wasmtime-wasi-http")]
174impl From<wasmtime_wasi_http::bindings::http::types::Scheme>
175    for bindings::wrpc::http::types::Scheme
176{
177    fn from(scheme: wasmtime_wasi_http::bindings::http::types::Scheme) -> Self {
178        match scheme {
179            wasmtime_wasi_http::bindings::http::types::Scheme::Http => Self::Http,
180            wasmtime_wasi_http::bindings::http::types::Scheme::Https => Self::Https,
181            wasmtime_wasi_http::bindings::http::types::Scheme::Other(other) => Self::Other(other),
182        }
183    }
184}
185
186#[cfg(feature = "wasmtime-wasi-http")]
187impl From<bindings::wrpc::http::types::Scheme>
188    for wasmtime_wasi_http::bindings::http::types::Scheme
189{
190    fn from(scheme: bindings::wrpc::http::types::Scheme) -> Self {
191        use bindings::wrpc::http::types::Scheme;
192
193        match scheme {
194            Scheme::Http => Self::Http,
195            Scheme::Https => Self::Https,
196            Scheme::Other(other) => Self::Other(other),
197        }
198    }
199}
200
201#[cfg(feature = "wasmtime-wasi-http")]
202impl From<wasmtime_wasi_http::bindings::http::types::DnsErrorPayload>
203    for bindings::wasi::http::types::DnsErrorPayload
204{
205    fn from(
206        wasmtime_wasi_http::bindings::http::types::DnsErrorPayload { rcode, info_code }: wasmtime_wasi_http::bindings::http::types::DnsErrorPayload,
207    ) -> Self {
208        Self { rcode, info_code }
209    }
210}
211
212#[cfg(feature = "wasmtime-wasi-http")]
213impl From<bindings::wasi::http::types::DnsErrorPayload>
214    for wasmtime_wasi_http::bindings::http::types::DnsErrorPayload
215{
216    fn from(
217        bindings::wasi::http::types::DnsErrorPayload { rcode, info_code }: bindings::wasi::http::types::DnsErrorPayload,
218    ) -> Self {
219        Self { rcode, info_code }
220    }
221}
222
223#[cfg(feature = "wasmtime-wasi-http")]
224impl From<wasmtime_wasi_http::bindings::http::types::TlsAlertReceivedPayload>
225    for bindings::wasi::http::types::TlsAlertReceivedPayload
226{
227    fn from(
228        wasmtime_wasi_http::bindings::http::types::TlsAlertReceivedPayload {
229            alert_id,
230            alert_message,
231        }: wasmtime_wasi_http::bindings::http::types::TlsAlertReceivedPayload,
232    ) -> Self {
233        Self {
234            alert_id,
235            alert_message,
236        }
237    }
238}
239
240#[cfg(feature = "wasmtime-wasi-http")]
241impl From<bindings::wasi::http::types::TlsAlertReceivedPayload>
242    for wasmtime_wasi_http::bindings::http::types::TlsAlertReceivedPayload
243{
244    fn from(
245        bindings::wasi::http::types::TlsAlertReceivedPayload {
246            alert_id,
247            alert_message,
248        }: bindings::wasi::http::types::TlsAlertReceivedPayload,
249    ) -> Self {
250        Self {
251            alert_id,
252            alert_message,
253        }
254    }
255}
256
257#[cfg(feature = "wasmtime-wasi-http")]
258impl From<wasmtime_wasi_http::bindings::http::types::FieldSizePayload>
259    for bindings::wasi::http::types::FieldSizePayload
260{
261    fn from(
262        wasmtime_wasi_http::bindings::http::types::FieldSizePayload {
263            field_name,
264            field_size,
265        }: wasmtime_wasi_http::bindings::http::types::FieldSizePayload,
266    ) -> Self {
267        Self {
268            field_name,
269            field_size,
270        }
271    }
272}
273
274#[cfg(feature = "wasmtime-wasi-http")]
275impl From<bindings::wasi::http::types::FieldSizePayload>
276    for wasmtime_wasi_http::bindings::http::types::FieldSizePayload
277{
278    fn from(
279        bindings::wasi::http::types::FieldSizePayload {
280            field_name,
281            field_size,
282        }: bindings::wasi::http::types::FieldSizePayload,
283    ) -> Self {
284        Self {
285            field_name,
286            field_size,
287        }
288    }
289}
290
291#[cfg(feature = "wasmtime-wasi-http")]
292impl From<wasmtime_wasi_http::bindings::http::types::ErrorCode>
293    for bindings::wasi::http::types::ErrorCode
294{
295    fn from(code: wasmtime_wasi_http::bindings::http::types::ErrorCode) -> Self {
296        use wasmtime_wasi_http::bindings::http::types;
297        match code {
298            types::ErrorCode::DnsTimeout => Self::DnsTimeout,
299            types::ErrorCode::DnsError(err) => Self::DnsError(err.into()),
300            types::ErrorCode::DestinationNotFound => Self::DestinationNotFound,
301            types::ErrorCode::DestinationUnavailable => Self::DestinationUnavailable,
302            types::ErrorCode::DestinationIpProhibited => Self::DestinationIpProhibited,
303            types::ErrorCode::DestinationIpUnroutable => Self::DestinationIpUnroutable,
304            types::ErrorCode::ConnectionRefused => Self::ConnectionRefused,
305            types::ErrorCode::ConnectionTerminated => Self::ConnectionTerminated,
306            types::ErrorCode::ConnectionTimeout => Self::ConnectionTimeout,
307            types::ErrorCode::ConnectionReadTimeout => Self::ConnectionReadTimeout,
308            types::ErrorCode::ConnectionWriteTimeout => Self::ConnectionWriteTimeout,
309            types::ErrorCode::ConnectionLimitReached => Self::ConnectionLimitReached,
310            types::ErrorCode::TlsProtocolError => Self::TlsProtocolError,
311            types::ErrorCode::TlsCertificateError => Self::TlsCertificateError,
312            types::ErrorCode::TlsAlertReceived(err) => Self::TlsAlertReceived(err.into()),
313            types::ErrorCode::HttpRequestDenied => Self::HttpRequestDenied,
314            types::ErrorCode::HttpRequestLengthRequired => Self::HttpRequestLengthRequired,
315            types::ErrorCode::HttpRequestBodySize(size) => Self::HttpRequestBodySize(size),
316            types::ErrorCode::HttpRequestMethodInvalid => Self::HttpRequestMethodInvalid,
317            types::ErrorCode::HttpRequestUriInvalid => Self::HttpRequestUriInvalid,
318            types::ErrorCode::HttpRequestUriTooLong => Self::HttpRequestUriTooLong,
319            types::ErrorCode::HttpRequestHeaderSectionSize(err) => {
320                Self::HttpRequestHeaderSectionSize(err)
321            }
322            types::ErrorCode::HttpRequestHeaderSize(err) => {
323                Self::HttpRequestHeaderSize(err.map(Into::into))
324            }
325            types::ErrorCode::HttpRequestTrailerSectionSize(err) => {
326                Self::HttpRequestTrailerSectionSize(err)
327            }
328            types::ErrorCode::HttpRequestTrailerSize(err) => {
329                Self::HttpRequestTrailerSize(err.into())
330            }
331            types::ErrorCode::HttpResponseIncomplete => Self::HttpResponseIncomplete,
332            types::ErrorCode::HttpResponseHeaderSectionSize(err) => {
333                Self::HttpResponseHeaderSectionSize(err)
334            }
335            types::ErrorCode::HttpResponseHeaderSize(err) => {
336                Self::HttpResponseHeaderSize(err.into())
337            }
338            types::ErrorCode::HttpResponseBodySize(err) => Self::HttpResponseBodySize(err),
339            types::ErrorCode::HttpResponseTrailerSectionSize(err) => {
340                Self::HttpResponseTrailerSectionSize(err)
341            }
342            types::ErrorCode::HttpResponseTrailerSize(err) => {
343                Self::HttpResponseTrailerSize(err.into())
344            }
345            types::ErrorCode::HttpResponseTransferCoding(err) => {
346                Self::HttpResponseTransferCoding(err)
347            }
348            types::ErrorCode::HttpResponseContentCoding(err) => {
349                Self::HttpResponseContentCoding(err)
350            }
351            types::ErrorCode::HttpResponseTimeout => Self::HttpResponseTimeout,
352            types::ErrorCode::HttpUpgradeFailed => Self::HttpUpgradeFailed,
353            types::ErrorCode::HttpProtocolError => Self::HttpProtocolError,
354            types::ErrorCode::LoopDetected => Self::LoopDetected,
355            types::ErrorCode::ConfigurationError => Self::ConfigurationError,
356            types::ErrorCode::InternalError(err) => Self::InternalError(err),
357        }
358    }
359}
360
361#[cfg(feature = "wasmtime-wasi-http")]
362impl From<bindings::wasi::http::types::ErrorCode>
363    for wasmtime_wasi_http::bindings::http::types::ErrorCode
364{
365    fn from(code: bindings::wasi::http::types::ErrorCode) -> Self {
366        use bindings::wasi::http::types::ErrorCode;
367
368        match code {
369            ErrorCode::DnsTimeout => Self::DnsTimeout,
370            ErrorCode::DnsError(err) => Self::DnsError(err.into()),
371            ErrorCode::DestinationNotFound => Self::DestinationNotFound,
372            ErrorCode::DestinationUnavailable => Self::DestinationUnavailable,
373            ErrorCode::DestinationIpProhibited => Self::DestinationIpProhibited,
374            ErrorCode::DestinationIpUnroutable => Self::DestinationIpUnroutable,
375            ErrorCode::ConnectionRefused => Self::ConnectionRefused,
376            ErrorCode::ConnectionTerminated => Self::ConnectionTerminated,
377            ErrorCode::ConnectionTimeout => Self::ConnectionTimeout,
378            ErrorCode::ConnectionReadTimeout => Self::ConnectionReadTimeout,
379            ErrorCode::ConnectionWriteTimeout => Self::ConnectionWriteTimeout,
380            ErrorCode::ConnectionLimitReached => Self::ConnectionLimitReached,
381            ErrorCode::TlsProtocolError => Self::TlsProtocolError,
382            ErrorCode::TlsCertificateError => Self::TlsCertificateError,
383            ErrorCode::TlsAlertReceived(err) => Self::TlsAlertReceived(err.into()),
384            ErrorCode::HttpRequestDenied => Self::HttpRequestDenied,
385            ErrorCode::HttpRequestLengthRequired => Self::HttpRequestLengthRequired,
386            ErrorCode::HttpRequestBodySize(size) => Self::HttpRequestBodySize(size),
387            ErrorCode::HttpRequestMethodInvalid => Self::HttpRequestMethodInvalid,
388            ErrorCode::HttpRequestUriInvalid => Self::HttpRequestUriInvalid,
389            ErrorCode::HttpRequestUriTooLong => Self::HttpRequestUriTooLong,
390            ErrorCode::HttpRequestHeaderSectionSize(err) => Self::HttpRequestHeaderSectionSize(err),
391            ErrorCode::HttpRequestHeaderSize(err) => {
392                Self::HttpRequestHeaderSize(err.map(Into::into))
393            }
394            ErrorCode::HttpRequestTrailerSectionSize(err) => {
395                Self::HttpRequestTrailerSectionSize(err)
396            }
397            ErrorCode::HttpRequestTrailerSize(err) => Self::HttpRequestTrailerSize(err.into()),
398            ErrorCode::HttpResponseIncomplete => Self::HttpResponseIncomplete,
399            ErrorCode::HttpResponseHeaderSectionSize(err) => {
400                Self::HttpResponseHeaderSectionSize(err)
401            }
402            ErrorCode::HttpResponseHeaderSize(err) => Self::HttpResponseHeaderSize(err.into()),
403            ErrorCode::HttpResponseBodySize(err) => Self::HttpResponseBodySize(err),
404            ErrorCode::HttpResponseTrailerSectionSize(err) => {
405                Self::HttpResponseTrailerSectionSize(err)
406            }
407            ErrorCode::HttpResponseTrailerSize(err) => Self::HttpResponseTrailerSize(err.into()),
408            ErrorCode::HttpResponseTransferCoding(err) => Self::HttpResponseTransferCoding(err),
409            ErrorCode::HttpResponseContentCoding(err) => Self::HttpResponseContentCoding(err),
410            ErrorCode::HttpResponseTimeout => Self::HttpResponseTimeout,
411            ErrorCode::HttpUpgradeFailed => Self::HttpUpgradeFailed,
412            ErrorCode::HttpProtocolError => Self::HttpProtocolError,
413            ErrorCode::LoopDetected => Self::LoopDetected,
414            ErrorCode::ConfigurationError => Self::ConfigurationError,
415            ErrorCode::InternalError(err) => Self::InternalError(err),
416        }
417    }
418}
419
420#[cfg(feature = "http-body")]
421pub struct HttpBody {
422    pub body: core::pin::Pin<Box<dyn futures::Stream<Item = bytes::Bytes> + Send>>,
423    pub trailers: core::pin::Pin<
424        Box<dyn core::future::Future<Output = Option<bindings::wrpc::http::types::Fields>> + Send>,
425    >,
426}
427
428#[cfg(feature = "http-body")]
429impl http_body::Body for HttpBody {
430    type Data = bytes::Bytes;
431    type Error = anyhow::Error;
432
433    fn poll_frame(
434        mut self: core::pin::Pin<&mut Self>,
435        cx: &mut core::task::Context<'_>,
436    ) -> core::task::Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
437        use anyhow::Context as _;
438        use core::task::Poll;
439        use futures::{FutureExt as _, StreamExt as _};
440
441        match self.body.poll_next_unpin(cx) {
442            Poll::Pending => Poll::Pending,
443            Poll::Ready(Some(buf)) => Poll::Ready(Some(Ok(http_body::Frame::data(buf)))),
444            Poll::Ready(None) => match self.trailers.poll_unpin(cx) {
445                Poll::Pending => Poll::Pending,
446                Poll::Ready(None) => Poll::Ready(None),
447                Poll::Ready(Some(trailers)) => {
448                    let trailers = try_fields_to_header_map(trailers)
449                        .context("failed to convert trailer fields to header map")?;
450                    Poll::Ready(Some(Ok(http_body::Frame::trailers(trailers))))
451                }
452            },
453        }
454    }
455}
456
457#[cfg(feature = "http-body")]
458pub enum HttpBodyError<E> {
459    InvalidFrame,
460    TrailerReceiverClosed,
461    HeaderConversion(anyhow::Error),
462    Body(E),
463}
464
465#[cfg(feature = "http-body")]
466impl<E: core::fmt::Debug> core::fmt::Debug for HttpBodyError<E> {
467    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
468        match self {
469            Self::InvalidFrame => write!(f, "frame is not valid"),
470            Self::TrailerReceiverClosed => write!(f, "trailer receiver is closed"),
471            Self::HeaderConversion(err) => write!(f, "failed to convert headers: {err:#}"),
472            Self::Body(err) => write!(f, "encountered a body error: {err:?}"),
473        }
474    }
475}
476
477#[cfg(feature = "http-body")]
478impl<E: core::fmt::Display> core::fmt::Display for HttpBodyError<E> {
479    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
480        match self {
481            Self::InvalidFrame => write!(f, "frame is not valid"),
482            Self::TrailerReceiverClosed => write!(f, "trailer receiver is closed"),
483            Self::HeaderConversion(err) => write!(f, "failed to convert headers: {err:#}"),
484            Self::Body(err) => write!(f, "encountered a body error: {err}"),
485        }
486    }
487}
488
489#[cfg(feature = "http-body")]
490impl<E: core::fmt::Debug + core::fmt::Display> std::error::Error for HttpBodyError<E> {}
491
492#[cfg(feature = "http-body")]
493#[tracing::instrument(level = "trace", skip_all)]
494pub fn split_http_body<E>(
495    body: impl http_body::Body<Data = bytes::Bytes, Error = E>,
496) -> (
497    impl futures::Stream<Item = Result<bytes::Bytes, HttpBodyError<E>>>,
498    impl core::future::Future<Output = Option<bindings::wrpc::http::types::Fields>>,
499) {
500    use futures::StreamExt as _;
501
502    let (trailers_tx, mut trailers_rx) = tokio::sync::mpsc::channel(1);
503    let body = http_body_util::BodyStream::new(body).filter_map(move |frame| {
504        let trailers_tx = trailers_tx.clone();
505        async move {
506            match frame {
507                Ok(frame) => match frame.into_data() {
508                    Ok(buf) => Some(Ok(buf)),
509                    Err(trailers) => match trailers.into_trailers() {
510                        Ok(trailers) => match try_header_map_to_fields(trailers) {
511                            Ok(trailers) => {
512                                if trailers_tx.send(trailers).await.is_err() {
513                                    Some(Err(HttpBodyError::TrailerReceiverClosed))
514                                } else {
515                                    None
516                                }
517                            }
518                            Err(err) => Some(Err(HttpBodyError::HeaderConversion(err))),
519                        },
520                        Err(_) => Some(Err(HttpBodyError::InvalidFrame)),
521                    },
522                },
523                Err(err) => Some(Err(HttpBodyError::Body(err))),
524            }
525        }
526    });
527    let trailers = async move { trailers_rx.recv().await };
528    (body, trailers)
529}
530
531#[cfg(feature = "http-body")]
532#[tracing::instrument(level = "trace", skip_all)]
533pub fn split_outgoing_http_body<E>(
534    body: impl http_body::Body<Data = bytes::Bytes, Error = E>,
535) -> (
536    impl futures::Stream<Item = bytes::Bytes>,
537    impl core::future::Future<Output = Option<bindings::wrpc::http::types::Fields>>,
538    impl futures::Stream<Item = HttpBodyError<E>>,
539) {
540    use futures::StreamExt as _;
541
542    let (body, trailers) = split_http_body(body);
543    let (errors_tx, errors_rx) = tokio::sync::mpsc::channel(1);
544    let body = body.filter_map(move |res| {
545        let errors_tx = errors_tx.clone();
546        async move {
547            match res {
548                Ok(buf) => Some(buf),
549                Err(err) => {
550                    if errors_tx.send(err).await.is_err() {
551                        tracing::trace!("failed to send body error");
552                    }
553                    None
554                }
555            }
556        }
557    });
558    let errors_rx = tokio_stream::wrappers::ReceiverStream::new(errors_rx);
559    (body, trailers, errors_rx)
560}
561
562#[cfg(feature = "http-body")]
563impl TryFrom<bindings::wrpc::http::types::Request> for http::Request<HttpBody> {
564    type Error = anyhow::Error;
565
566    fn try_from(
567        bindings::wrpc::http::types::Request {
568            body,
569            trailers,
570            method,
571            path_with_query,
572            scheme,
573            authority,
574            headers,
575        }: bindings::wrpc::http::types::Request,
576    ) -> Result<Self, Self::Error> {
577        use anyhow::Context as _;
578
579        let uri = http::Uri::builder();
580        let uri = if let Some(path_with_query) = path_with_query {
581            uri.path_and_query(path_with_query)
582        } else {
583            uri.path_and_query("/")
584        };
585        let uri = if let Some(scheme) = scheme {
586            let scheme =
587                http::uri::Scheme::try_from(&scheme).context("failed to convert scheme")?;
588            uri.scheme(scheme)
589        } else {
590            uri
591        };
592        let uri = if let Some(authority) = authority {
593            uri.authority(authority)
594        } else {
595            uri
596        };
597        let uri = uri.build().context("failed to build URI")?;
598        let method = http::method::Method::try_from(&method).context("failed to convert method")?;
599        let mut req = http::Request::builder().method(method).uri(uri);
600        let req_headers = req
601            .headers_mut()
602            .context("failed to construct header map")?;
603        *req_headers = try_fields_to_header_map(headers)
604            .context("failed to convert header fields to header map")?;
605        req.body(HttpBody { body, trailers })
606            .context("failed to construct request")
607    }
608}
609
610#[cfg(feature = "wasmtime-wasi-http")]
611impl TryFrom<bindings::wrpc::http::types::Request>
612    for http::Request<
613        http_body_util::combinators::UnsyncBoxBody<
614            bytes::Bytes,
615            wasmtime_wasi_http::bindings::http::types::ErrorCode,
616        >,
617    >
618{
619    type Error = anyhow::Error;
620
621    fn try_from(req: bindings::wrpc::http::types::Request) -> Result<Self, Self::Error> {
622        use http_body_util::BodyExt as _;
623
624        let req: http::Request<HttpBody> = req.try_into()?;
625        Ok(req.map(|HttpBody { body, trailers }| {
626            http_body_util::combinators::UnsyncBoxBody::new(HttpBody { body, trailers }.map_err(
627                |err| {
628                    wasmtime_wasi_http::bindings::http::types::ErrorCode::InternalError(Some(
629                        format!("{err:#}"),
630                    ))
631                },
632            ))
633        }))
634    }
635}
636
637#[cfg(feature = "wasmtime-wasi-http")]
638struct SyncBody<D, E>(std::sync::Mutex<http_body_util::combinators::UnsyncBoxBody<D, E>>);
639
640#[cfg(feature = "wasmtime-wasi-http")]
641impl<D: bytes::Buf, E> http_body::Body for SyncBody<D, E> {
642    type Data = D;
643    type Error = E;
644
645    fn poll_frame(
646        self: core::pin::Pin<&mut Self>,
647        cx: &mut core::task::Context<'_>,
648    ) -> core::task::Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
649        let mut body = self.0.lock().unwrap();
650        core::pin::pin!(&mut *body).poll_frame(cx)
651    }
652}
653
654#[cfg(feature = "wasmtime-wasi-http")]
655impl TryFrom<bindings::wrpc::http::types::Request>
656    for http::Request<
657        http_body_util::combinators::BoxBody<
658            bytes::Bytes,
659            wasmtime_wasi_http::bindings::http::types::ErrorCode,
660        >,
661    >
662{
663    type Error = anyhow::Error;
664
665    fn try_from(req: bindings::wrpc::http::types::Request) -> Result<Self, Self::Error> {
666        let req: http::Request<http_body_util::combinators::UnsyncBoxBody<_, _>> =
667            req.try_into()?;
668        Ok(req.map(|body| {
669            http_body_util::combinators::BoxBody::new(SyncBody(std::sync::Mutex::new(body)))
670        }))
671    }
672}
673
674/// Attempt converting [`http::Request`] to [`Request`].
675/// Values of `path_with_query`, `scheme` and `authority` will be taken from the
676/// request URI
677#[cfg(feature = "http-body")]
678#[tracing::instrument(level = "trace", skip_all)]
679pub fn try_http_to_request<E>(
680    request: http::Request<impl http_body::Body<Data = bytes::Bytes, Error = E> + Send + 'static>,
681) -> anyhow::Result<(
682    bindings::wrpc::http::types::Request,
683    impl futures::Stream<Item = HttpBodyError<E>>,
684)>
685where
686    E: Send + 'static,
687{
688    let (
689        http::request::Parts {
690            ref method,
691            uri,
692            headers,
693            ..
694        },
695        body,
696    ) = request.into_parts();
697    let headers = try_header_map_to_fields(headers)?;
698    let (body, trailers, errors) = split_outgoing_http_body(body);
699    Ok((
700        bindings::wrpc::http::types::Request {
701            body: Box::pin(body),
702            trailers: Box::pin(trailers),
703            method: method.into(),
704            path_with_query: uri.path_and_query().map(ToString::to_string),
705            scheme: uri.scheme().map(Into::into),
706            authority: uri.authority().map(ToString::to_string),
707            headers,
708        },
709        errors,
710    ))
711}
712
713/// Attempt converting [`http::Request<wasmtime_wasi_http::body::HyperOutgoingBody>`] to [`bindings::wrpc::http::types::Request`].
714/// Values of `path_with_query`, `scheme` and `authority` will be taken from the
715/// request URI.
716#[cfg(feature = "wasmtime-wasi-http")]
717#[tracing::instrument(level = "trace", skip_all)]
718pub fn try_wasmtime_to_outgoing_request(
719    req: http::Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
720    wasmtime_wasi_http::types::OutgoingRequestConfig {
721        use_tls: _,
722        connect_timeout,
723        first_byte_timeout,
724        between_bytes_timeout,
725    }: wasmtime_wasi_http::types::OutgoingRequestConfig,
726) -> anyhow::Result<(
727    bindings::wrpc::http::types::Request,
728    bindings::wrpc::http::types::RequestOptions,
729    impl futures::Stream<Item = HttpBodyError<wasmtime_wasi_http::bindings::http::types::ErrorCode>>
730        + Send,
731)> {
732    use anyhow::Context as _;
733
734    let (req, errors) = try_http_to_request(req)?;
735    let connect_timeout = connect_timeout
736        .as_nanos()
737        .try_into()
738        .context("`connect_timeout` nanoseconds do not fit in u64")?;
739    let first_byte_timeout = first_byte_timeout
740        .as_nanos()
741        .try_into()
742        .context("`first_byte_timeout` nanoseconds do not fit in u64")?;
743    let between_bytes_timeout = between_bytes_timeout
744        .as_nanos()
745        .try_into()
746        .context("`between_bytes_timeout` nanoseconds do not fit in u64")?;
747    Ok((
748        req,
749        bindings::wrpc::http::types::RequestOptions {
750            connect_timeout: Some(connect_timeout),
751            first_byte_timeout: Some(first_byte_timeout),
752            between_bytes_timeout: Some(between_bytes_timeout),
753        },
754        errors,
755    ))
756}
757
758#[cfg(feature = "http-body")]
759impl TryFrom<bindings::wrpc::http::types::Response> for http::Response<HttpBody> {
760    type Error = anyhow::Error;
761
762    fn try_from(
763        bindings::wrpc::http::types::Response {
764            body,
765            trailers,
766            status,
767            headers,
768        }: bindings::wrpc::http::types::Response,
769    ) -> Result<Self, Self::Error> {
770        use anyhow::Context as _;
771
772        let mut resp = http::Response::builder().status(status);
773        let resp_headers = resp
774            .headers_mut()
775            .context("failed to construct header map")?;
776        *resp_headers = try_fields_to_header_map(headers)
777            .context("failed to convert header fields to header map")?;
778        resp.body(HttpBody { body, trailers })
779            .context("failed to construct response")
780    }
781}
782
783#[cfg(feature = "wasmtime-wasi-http")]
784impl TryFrom<bindings::wrpc::http::types::Response>
785    for http::Response<
786        http_body_util::combinators::UnsyncBoxBody<
787            bytes::Bytes,
788            wasmtime_wasi_http::bindings::http::types::ErrorCode,
789        >,
790    >
791{
792    type Error = anyhow::Error;
793
794    fn try_from(
795        bindings::wrpc::http::types::Response {
796            body,
797            trailers,
798            status,
799            headers,
800        }: bindings::wrpc::http::types::Response,
801    ) -> Result<Self, Self::Error> {
802        use anyhow::Context as _;
803        use http_body_util::BodyExt as _;
804
805        let mut resp = http::Response::builder().status(status);
806        let resp_headers = resp
807            .headers_mut()
808            .context("failed to construct header map")?;
809        *resp_headers = try_fields_to_header_map(headers)
810            .context("failed to convert header fields to header map")?;
811        let trailers = tokio::spawn(trailers);
812        resp.body(http_body_util::combinators::UnsyncBoxBody::new(
813            HttpBody {
814                body,
815                trailers: Box::pin(async { trailers.await.unwrap() }),
816            }
817            .map_err(|err| {
818                wasmtime_wasi_http::bindings::http::types::ErrorCode::InternalError(Some(format!(
819                    "{err:#}"
820                )))
821            }),
822        ))
823        .context("failed to construct response")
824    }
825}
826
827#[cfg(feature = "wasmtime-wasi-http")]
828impl TryFrom<bindings::wrpc::http::types::Response>
829    for http::Response<
830        http_body_util::combinators::BoxBody<
831            bytes::Bytes,
832            wasmtime_wasi_http::bindings::http::types::ErrorCode,
833        >,
834    >
835{
836    type Error = anyhow::Error;
837
838    fn try_from(resp: bindings::wrpc::http::types::Response) -> Result<Self, Self::Error> {
839        let resp: http::Response<http_body_util::combinators::UnsyncBoxBody<_, _>> =
840            resp.try_into()?;
841        Ok(resp.map(|body| {
842            http_body_util::combinators::BoxBody::new(SyncBody(std::sync::Mutex::new(body)))
843        }))
844    }
845}
846
847#[cfg(feature = "http-body")]
848#[tracing::instrument(level = "trace", skip_all)]
849pub fn try_http_to_response<E>(
850    response: http::Response<impl http_body::Body<Data = bytes::Bytes, Error = E> + Send + 'static>,
851) -> anyhow::Result<(
852    bindings::wrpc::http::types::Response,
853    impl futures::Stream<Item = HttpBodyError<E>>,
854)>
855where
856    E: Send + 'static,
857{
858    let (
859        http::response::Parts {
860            status, headers, ..
861        },
862        body,
863    ) = response.into_parts();
864    let headers = try_header_map_to_fields(headers)?;
865    let (body, trailers, errors) = split_outgoing_http_body(body);
866    Ok((
867        bindings::wrpc::http::types::Response {
868            body: Box::pin(body),
869            trailers: Box::pin(trailers),
870            status: status.into(),
871            headers,
872        },
873        errors,
874    ))
875}
876
877pub trait InvokeIncomingHandler: wrpc_transport::Invoke {
878    #[cfg(feature = "http-body")]
879    #[tracing::instrument(level = "trace", skip_all)]
880    fn invoke_handle_http<E>(
881        &self,
882        cx: Self::Context,
883        request: http::Request<
884            impl http_body::Body<Data = bytes::Bytes, Error = E> + Send + 'static,
885        >,
886    ) -> impl core::future::Future<
887        Output = anyhow::Result<(
888            Result<http::Response<HttpBody>, bindings::wrpc::http::types::ErrorCode>,
889            impl futures::Stream<Item = HttpBodyError<E>> + 'static,
890            Option<impl core::future::Future<Output = anyhow::Result<()>> + 'static>,
891        )>,
892    >
893    where
894        Self: Sized,
895        E: Send + 'static,
896    {
897        use anyhow::Context as _;
898
899        async {
900            let (request, errors) = try_http_to_request(request)
901                .context("failed to convert `http` request to `wrpc:http/types.request`")?;
902            let (response, io) = bindings::wrpc::http::incoming_handler::handle(self, cx, request)
903                .await
904                .context("failed to invoke `wrpc:http/incoming-handler.handle`")?;
905            match response {
906                Ok(response) => {
907                    let response = response.try_into().context(
908                        "failed to convert `wrpc:http/types.response` to `http` response",
909                    )?;
910                    Ok((Ok(response), errors, io))
911                }
912                Err(code) => Ok((Err(code), errors, io)),
913            }
914        }
915    }
916}
917
918impl<T: wrpc_transport::Invoke> InvokeIncomingHandler for T {}
919
920pub trait InvokeOutgoingHandler: wrpc_transport::Invoke {
921    #[cfg(feature = "wasmtime-wasi-http")]
922    #[tracing::instrument(level = "trace", skip_all)]
923    fn invoke_handle_wasmtime(
924        &self,
925        cx: Self::Context,
926        request: http::Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
927        options: wasmtime_wasi_http::types::OutgoingRequestConfig,
928    ) -> impl core::future::Future<
929        Output = anyhow::Result<(
930            Result<
931                http::Response<wasmtime_wasi_http::body::HyperIncomingBody>,
932                wasmtime_wasi_http::bindings::http::types::ErrorCode,
933            >,
934            impl futures::Stream<
935                    Item = HttpBodyError<wasmtime_wasi_http::bindings::http::types::ErrorCode>,
936                > + 'static,
937            Option<impl core::future::Future<Output = anyhow::Result<()>> + 'static>,
938        )>,
939    >
940    where
941        Self: Sized,
942    {
943        use anyhow::Context as _;
944
945        async {
946            let (req, opts, errors) = try_wasmtime_to_outgoing_request(request, options)?;
947            let (resp, io) =
948                bindings::wrpc::http::outgoing_handler::handle(self, cx, req, Some(opts))
949                    .await
950                    .context("failed to invoke `wrpc:http/outgoing-handler.handle`")?;
951            match resp {
952                Ok(resp) => {
953                    let resp = resp.try_into().context(
954                        "failed to convert `wrpc:http` response to Wasmtime `wasi:http`",
955                    )?;
956                    Ok((Ok(resp), errors, io))
957                }
958                Err(code) => Ok((Err(code.into()), errors, io)),
959            }
960        }
961    }
962}
963
964impl<T: wrpc_transport::Invoke> InvokeOutgoingHandler for T {}
965
966#[cfg(feature = "http-body")]
967#[derive(Copy, Clone, Debug)]
968pub struct ServeHttp<T>(pub T);
969
970#[cfg(feature = "http-body")]
971pub trait ServeIncomingHandlerHttp<Ctx> {
972    fn handle(
973        &self,
974        cx: Ctx,
975        request: http::Request<HttpBody>,
976    ) -> impl core::future::Future<
977        Output = anyhow::Result<
978            Result<
979                http::Response<
980                    impl http_body::Body<Data = bytes::Bytes, Error = core::convert::Infallible>
981                        + Send
982                        + 'static,
983                >,
984                bindings::wasi::http::types::ErrorCode,
985            >,
986        >,
987    > + Send;
988}
989
990#[cfg(feature = "http-body")]
991impl<Ctx, T> bindings::exports::wrpc::http::incoming_handler::Handler<Ctx> for ServeHttp<T>
992where
993    T: ServeIncomingHandlerHttp<Ctx> + Sync,
994    Ctx: Send,
995{
996    async fn handle(
997        &self,
998        cx: Ctx,
999        request: bindings::wrpc::http::types::Request,
1000    ) -> anyhow::Result<
1001        Result<bindings::wrpc::http::types::Response, bindings::wasi::http::types::ErrorCode>,
1002    > {
1003        use anyhow::Context as _;
1004        use futures::StreamExt as _;
1005
1006        let request = request
1007            .try_into()
1008            .context("failed to convert incoming `wrpc:http/types.request` to `http` request")?;
1009        match ServeIncomingHandlerHttp::handle(&self.0, cx, request).await? {
1010            Ok(response) => {
1011                let (response, errs) = try_http_to_response(response).context(
1012                    "failed to convert outgoing `http` response to `wrpc:http/types.response`",
1013                )?;
1014                tokio::spawn(errs.for_each_concurrent(None, |err| async move {
1015                    tracing::error!(?err, "response body processing error encountered");
1016                }));
1017                Ok(Ok(response))
1018            }
1019            Err(code) => Ok(Err(code)),
1020        }
1021    }
1022}
1023
1024#[cfg(feature = "wasmtime-wasi-http")]
1025#[derive(Copy, Clone, Debug)]
1026pub struct ServeWasmtime<T>(pub T);
1027
1028#[cfg(feature = "wasmtime-wasi-http")]
1029pub trait ServeIncomingHandlerWasmtime<Ctx> {
1030    fn handle(
1031        &self,
1032        cx: Ctx,
1033        request: http::Request<wasmtime_wasi_http::body::HyperIncomingBody>,
1034    ) -> impl core::future::Future<
1035        Output = anyhow::Result<
1036            Result<
1037                http::Response<wasmtime_wasi_http::body::HyperOutgoingBody>,
1038                wasmtime_wasi_http::bindings::http::types::ErrorCode,
1039            >,
1040        >,
1041    > + Send;
1042}
1043
1044#[cfg(feature = "wasmtime-wasi-http")]
1045impl<Ctx, T> bindings::exports::wrpc::http::incoming_handler::Handler<Ctx> for ServeWasmtime<T>
1046where
1047    T: ServeIncomingHandlerWasmtime<Ctx> + Sync,
1048    Ctx: Send,
1049{
1050    async fn handle(
1051        &self,
1052        cx: Ctx,
1053        request: bindings::wrpc::http::types::Request,
1054    ) -> anyhow::Result<
1055        Result<bindings::wrpc::http::types::Response, bindings::wasi::http::types::ErrorCode>,
1056    > {
1057        use anyhow::Context as _;
1058        use futures::StreamExt as _;
1059
1060        let request = request.try_into().context(
1061            "failed to convert incoming `wrpc:http/types.request` to `wasmtime-wasi-http` request",
1062        )?;
1063        match ServeIncomingHandlerWasmtime::handle(&self.0, cx, request).await? {
1064            Ok(response) => {
1065                let (response, errs) = try_http_to_response(response).context(
1066                    "failed to convert outgoing `http` response to `wrpc:http/types.response`",
1067                )?;
1068                tokio::spawn(errs.for_each_concurrent(None, |err| async move {
1069                    tracing::error!(?err, "response body processing error encountered");
1070                }));
1071                Ok(Ok(response))
1072            }
1073            Err(code) => Ok(Err(code.into())),
1074        }
1075    }
1076}
1077
1078#[cfg(feature = "http-body")]
1079pub trait ServeOutgoingHandlerHttp<Ctx> {
1080    fn handle(
1081        &self,
1082        cx: Ctx,
1083        request: http::Request<HttpBody>,
1084        options: Option<bindings::wrpc::http::types::RequestOptions>,
1085    ) -> impl core::future::Future<
1086        Output = anyhow::Result<
1087            Result<
1088                http::Response<
1089                    impl http_body::Body<Data = bytes::Bytes, Error = core::convert::Infallible>
1090                        + Send
1091                        + 'static,
1092                >,
1093                bindings::wasi::http::types::ErrorCode,
1094            >,
1095        >,
1096    > + Send;
1097}
1098
1099#[cfg(feature = "http-body")]
1100impl<Ctx, T> bindings::exports::wrpc::http::outgoing_handler::Handler<Ctx> for ServeHttp<T>
1101where
1102    T: ServeOutgoingHandlerHttp<Ctx> + Sync,
1103    Ctx: Send,
1104{
1105    async fn handle(
1106        &self,
1107        cx: Ctx,
1108        request: bindings::wrpc::http::types::Request,
1109        options: Option<bindings::wrpc::http::types::RequestOptions>,
1110    ) -> anyhow::Result<
1111        Result<bindings::wrpc::http::types::Response, bindings::wasi::http::types::ErrorCode>,
1112    > {
1113        use anyhow::Context as _;
1114        use futures::StreamExt as _;
1115
1116        let request = request
1117            .try_into()
1118            .context("failed to convert incoming `wrpc:http/types.request` to `http` request")?;
1119        match ServeOutgoingHandlerHttp::handle(&self.0, cx, request, options).await? {
1120            Ok(response) => {
1121                let (response, errs) = try_http_to_response(response).context(
1122                    "failed to convert outgoing `http` response to `wrpc:http/types.response`",
1123                )?;
1124                tokio::spawn(errs.for_each_concurrent(None, |err| async move {
1125                    tracing::error!(?err, "response body processing error encountered");
1126                }));
1127                Ok(Ok(response))
1128            }
1129            Err(code) => Ok(Err(code)),
1130        }
1131    }
1132}
1133
1134#[cfg(test)]
1135#[allow(unused)]
1136mod tests {
1137    use core::{pin::pin, time::Duration};
1138
1139    use futures::StreamExt as _;
1140    use http_body_util::BodyExt as _;
1141    use tokio::join;
1142
1143    use super::*;
1144
1145    struct Handler;
1146
1147    fn lifetimes_wasmtime(
1148        clt: &impl wrpc_transport::Invoke<Context = ()>,
1149        request: http::Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
1150        config: wasmtime_wasi_http::types::OutgoingRequestConfig,
1151    ) -> impl core::future::Future<
1152        Output = anyhow::Result<(
1153            Result<
1154                http::Response<wasmtime_wasi_http::body::HyperIncomingBody>,
1155                wasmtime_wasi_http::bindings::http::types::ErrorCode,
1156            >,
1157            tokio::task::JoinHandle<()>,
1158        )>,
1159    > + Send
1160           + wrpc_transport::Captures<'_> {
1161        use super::InvokeOutgoingHandler as _;
1162        use futures::StreamExt as _;
1163
1164        async {
1165            let (resp, errs, io) = match clt.invoke_handle_wasmtime((), request, config).await? {
1166                (Ok(resp), errs, io) => (resp, errs, io),
1167                (Err(err), _, _) => anyhow::bail!(err),
1168            };
1169            let worker = tokio::spawn(async move {
1170                tokio::join!(errs.for_each(|err| async move { _ = err }), async move {
1171                    if let Some(io) = io {
1172                        if let Err(err) = io.await {
1173                            _ = err;
1174                        }
1175                    }
1176                });
1177            });
1178            Ok((Ok(resp), worker))
1179        }
1180    }
1181
1182    #[tokio::test]
1183    async fn test_split_http_body() {
1184        let (body, trailers) = split_http_body(http_body_util::Empty::new());
1185        assert!(pin!(body).next().await.is_none());
1186        assert_eq!(trailers.await, None);
1187    }
1188
1189    #[tokio::test]
1190    async fn test_split_outgoing_http_body() {
1191        let (body, trailers, mut errs) = split_outgoing_http_body(http_body_util::Empty::new());
1192        assert_eq!(pin!(body).next().await, None);
1193        assert_eq!(trailers.await, None);
1194        assert!(errs.next().await.is_none());
1195    }
1196
1197    #[tokio::test]
1198    async fn test_try_http_to_request() {
1199        let (
1200            bindings::wrpc::http::types::Request {
1201                mut body,
1202                mut trailers,
1203                method,
1204                path_with_query,
1205                scheme,
1206                authority,
1207                headers,
1208            },
1209            mut errs,
1210        ) = try_http_to_request(http::Request::new(http_body_util::Empty::new()))
1211            .expect("failed to convert empty request");
1212        assert_eq!(pin!(body).next().await, None);
1213        assert_eq!(trailers.await, None);
1214        assert!(errs.next().await.is_none());
1215        assert!(matches!(method, bindings::wrpc::http::types::Method::Get));
1216        assert_eq!(path_with_query.as_deref(), Some("/"));
1217        assert_eq!(authority, None);
1218        assert_eq!(headers, []);
1219    }
1220
1221    #[tokio::test]
1222    async fn test_try_wasmtime_to_outgoing_request() {
1223        let (
1224            bindings::wrpc::http::types::Request {
1225                mut body,
1226                mut trailers,
1227                method,
1228                path_with_query,
1229                scheme,
1230                authority,
1231                headers,
1232            },
1233            bindings::wrpc::http::types::RequestOptions {
1234                connect_timeout,
1235                first_byte_timeout,
1236                between_bytes_timeout,
1237            },
1238            mut errs,
1239        ) = try_wasmtime_to_outgoing_request(
1240            http::Request::new(wasmtime_wasi_http::body::HyperOutgoingBody::new(
1241                http_body_util::Empty::new().map_err(|_| {
1242                    wasmtime_wasi_http::bindings::http::types::ErrorCode::InternalError(None)
1243                }),
1244            )),
1245            wasmtime_wasi_http::types::OutgoingRequestConfig {
1246                use_tls: false,
1247                connect_timeout: Duration::from_secs(1),
1248                first_byte_timeout: Duration::from_secs(2),
1249                between_bytes_timeout: Duration::from_secs(3),
1250            },
1251        )
1252        .expect("failed to convert empty request");
1253        assert_eq!(pin!(body).next().await, None);
1254        assert_eq!(trailers.await, None);
1255        assert!(errs.next().await.is_none());
1256        assert!(matches!(method, bindings::wrpc::http::types::Method::Get));
1257        assert_eq!(path_with_query.as_deref(), Some("/"));
1258        assert_eq!(authority, None);
1259        assert_eq!(headers, []);
1260        assert_eq!(
1261            connect_timeout.map(Into::into),
1262            Some(Duration::from_secs(1).as_nanos())
1263        );
1264        assert_eq!(
1265            first_byte_timeout.map(Into::into),
1266            Some(Duration::from_secs(2).as_nanos())
1267        );
1268        assert_eq!(
1269            between_bytes_timeout.map(Into::into),
1270            Some(Duration::from_secs(3).as_nanos())
1271        );
1272    }
1273}