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#[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#[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}