wasmtime_wasi_http/p3/host/
types.rs

1use crate::p3::bindings::clocks::monotonic_clock::Duration;
2use crate::p3::bindings::http::types::{
3    ErrorCode, FieldName, FieldValue, Fields, HeaderError, Headers, Host, HostFields, HostRequest,
4    HostRequestOptions, HostRequestWithStore, HostResponse, HostResponseWithStore, Method, Request,
5    RequestOptions, RequestOptionsError, Response, Scheme, StatusCode, Trailers,
6};
7use crate::p3::body::{Body, HostBodyStreamProducer};
8use crate::p3::{HeaderResult, HttpError, RequestOptionsResult, WasiHttp, WasiHttpCtxView};
9use anyhow::Context as _;
10use core::mem;
11use core::pin::Pin;
12use core::task::{Context, Poll, ready};
13use http::header::CONTENT_LENGTH;
14use std::sync::Arc;
15use tokio::sync::oneshot;
16use wasmtime::component::{
17    Access, FutureProducer, FutureReader, Resource, ResourceTable, StreamReader,
18};
19use wasmtime::{AsContextMut, StoreContextMut};
20
21fn get_fields<'a>(
22    table: &'a ResourceTable,
23    fields: &Resource<Fields>,
24) -> wasmtime::Result<&'a Fields> {
25    table
26        .get(&fields)
27        .context("failed to get fields from table")
28}
29
30fn get_fields_mut<'a>(
31    table: &'a mut ResourceTable,
32    fields: &Resource<Fields>,
33) -> HeaderResult<&'a mut Fields> {
34    table
35        .get_mut(&fields)
36        .context("failed to get fields from table")
37        .map_err(crate::p3::HeaderError::trap)
38}
39
40fn push_fields(table: &mut ResourceTable, fields: Fields) -> wasmtime::Result<Resource<Fields>> {
41    table.push(fields).context("failed to push fields to table")
42}
43
44fn delete_fields(table: &mut ResourceTable, fields: Resource<Fields>) -> wasmtime::Result<Fields> {
45    table
46        .delete(fields)
47        .context("failed to delete fields from table")
48}
49
50fn get_request<'a>(
51    table: &'a ResourceTable,
52    req: &Resource<Request>,
53) -> wasmtime::Result<&'a Request> {
54    table.get(req).context("failed to get request from table")
55}
56
57fn get_request_mut<'a>(
58    table: &'a mut ResourceTable,
59    req: &Resource<Request>,
60) -> wasmtime::Result<&'a mut Request> {
61    table
62        .get_mut(req)
63        .context("failed to get request from table")
64}
65
66fn get_response<'a>(
67    table: &'a ResourceTable,
68    res: &Resource<Response>,
69) -> wasmtime::Result<&'a Response> {
70    table.get(res).context("failed to get response from table")
71}
72
73fn get_response_mut<'a>(
74    table: &'a mut ResourceTable,
75    res: &Resource<Response>,
76) -> wasmtime::Result<&'a mut Response> {
77    table
78        .get_mut(res)
79        .context("failed to get response from table")
80}
81
82fn get_request_options<'a>(
83    table: &'a ResourceTable,
84    opts: &Resource<RequestOptions>,
85) -> wasmtime::Result<&'a RequestOptions> {
86    table
87        .get(opts)
88        .context("failed to get request options from table")
89}
90
91fn get_request_options_mut<'a>(
92    table: &'a mut ResourceTable,
93    opts: &Resource<RequestOptions>,
94) -> RequestOptionsResult<&'a mut RequestOptions> {
95    table
96        .get_mut(opts)
97        .context("failed to get request options from table")
98        .map_err(crate::p3::RequestOptionsError::trap)
99}
100
101fn push_request_options(
102    table: &mut ResourceTable,
103    opts: RequestOptions,
104) -> wasmtime::Result<Resource<RequestOptions>> {
105    table
106        .push(opts)
107        .context("failed to push request options to table")
108}
109
110fn delete_request_options(
111    table: &mut ResourceTable,
112    opts: Resource<RequestOptions>,
113) -> wasmtime::Result<RequestOptions> {
114    table
115        .delete(opts)
116        .context("failed to delete request options from table")
117}
118
119fn parse_header_value(
120    name: &http::HeaderName,
121    value: impl AsRef<[u8]>,
122) -> Result<http::HeaderValue, HeaderError> {
123    if name == CONTENT_LENGTH {
124        let s = str::from_utf8(value.as_ref()).or(Err(HeaderError::InvalidSyntax))?;
125        let v: u64 = s.parse().or(Err(HeaderError::InvalidSyntax))?;
126        Ok(v.into())
127    } else {
128        http::HeaderValue::from_bytes(value.as_ref()).or(Err(HeaderError::InvalidSyntax))
129    }
130}
131
132enum GuestBodyResultProducer {
133    Receiver(oneshot::Receiver<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
134    Future(Pin<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
135}
136
137fn poll_future<T>(
138    cx: &mut Context<'_>,
139    fut: Pin<&mut (impl Future<Output = T> + ?Sized)>,
140    finish: bool,
141) -> Poll<Option<T>> {
142    match fut.poll(cx) {
143        Poll::Ready(v) => Poll::Ready(Some(v)),
144        Poll::Pending if finish => Poll::Ready(None),
145        Poll::Pending => Poll::Pending,
146    }
147}
148
149impl<D> FutureProducer<D> for GuestBodyResultProducer {
150    type Item = Result<(), ErrorCode>;
151
152    fn poll_produce(
153        mut self: Pin<&mut Self>,
154        cx: &mut Context<'_>,
155        _: StoreContextMut<D>,
156        finish: bool,
157    ) -> Poll<wasmtime::Result<Option<Self::Item>>> {
158        match &mut *self {
159            Self::Receiver(rx) => {
160                match ready!(poll_future(cx, Pin::new(rx), finish)) {
161                    Some(Ok(fut)) => {
162                        let mut fut = Box::into_pin(fut);
163                        // poll the received future once and update state
164                        let res = poll_future(cx, fut.as_mut(), finish);
165                        *self = Self::Future(fut);
166                        res.map(Ok)
167                    }
168                    Some(Err(..)) => {
169                        // oneshot sender dropped, treat as success
170                        Poll::Ready(Ok(Some(Ok(()))))
171                    }
172                    None => Poll::Ready(Ok(None)),
173                }
174            }
175            Self::Future(fut) => poll_future(cx, fut.as_mut(), finish).map(Ok),
176        }
177    }
178}
179
180impl HostFields for WasiHttpCtxView<'_> {
181    fn new(&mut self) -> wasmtime::Result<Resource<Fields>> {
182        push_fields(self.table, Fields::new_mutable_default())
183    }
184
185    fn from_list(
186        &mut self,
187        entries: Vec<(FieldName, FieldValue)>,
188    ) -> HeaderResult<Resource<Fields>> {
189        let mut fields = http::HeaderMap::default();
190        for (name, value) in entries {
191            let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
192            if self.ctx.is_forbidden_header(&name) {
193                return Err(HeaderError::Forbidden.into());
194            }
195            let value = parse_header_value(&name, value)?;
196            fields.append(name, value);
197        }
198        let fields = push_fields(self.table, Fields::new_mutable(fields))
199            .map_err(crate::p3::HeaderError::trap)?;
200        Ok(fields)
201    }
202
203    fn get(
204        &mut self,
205        fields: Resource<Fields>,
206        name: FieldName,
207    ) -> wasmtime::Result<Vec<FieldValue>> {
208        let fields = get_fields(self.table, &fields)?;
209        Ok(fields
210            .get_all(name)
211            .into_iter()
212            .map(|val| val.as_bytes().into())
213            .collect())
214    }
215
216    fn has(&mut self, fields: Resource<Fields>, name: FieldName) -> wasmtime::Result<bool> {
217        let fields = get_fields(self.table, &fields)?;
218        Ok(fields.contains_key(name))
219    }
220
221    fn set(
222        &mut self,
223        fields: Resource<Fields>,
224        name: FieldName,
225        value: Vec<FieldValue>,
226    ) -> HeaderResult<()> {
227        let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
228        if self.ctx.is_forbidden_header(&name) {
229            return Err(HeaderError::Forbidden.into());
230        }
231        let mut values = Vec::with_capacity(value.len());
232        for value in value {
233            let value = parse_header_value(&name, value)?;
234            values.push(value);
235        }
236        let fields = get_fields_mut(self.table, &fields)?;
237        let fields = fields.get_mut().ok_or(HeaderError::Immutable)?;
238        fields.remove(&name);
239        for value in values {
240            fields.append(&name, value);
241        }
242        Ok(())
243    }
244
245    fn delete(&mut self, fields: Resource<Fields>, name: FieldName) -> HeaderResult<()> {
246        let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
247        if self.ctx.is_forbidden_header(&name) {
248            return Err(HeaderError::Forbidden.into());
249        }
250        let fields = get_fields_mut(self.table, &fields)?;
251        let fields = fields.get_mut().ok_or(HeaderError::Immutable)?;
252        fields.remove(&name);
253        Ok(())
254    }
255
256    fn get_and_delete(
257        &mut self,
258        fields: Resource<Fields>,
259        name: FieldName,
260    ) -> HeaderResult<Vec<FieldValue>> {
261        let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
262        if self.ctx.is_forbidden_header(&name) {
263            return Err(HeaderError::Forbidden.into());
264        }
265        let fields = get_fields_mut(self.table, &fields)?;
266        let fields = fields.get_mut().ok_or(HeaderError::Immutable)?;
267        let http::header::Entry::Occupied(entry) = fields.entry(name) else {
268            return Ok(Vec::default());
269        };
270        let (.., values) = entry.remove_entry_mult();
271        Ok(values.map(|value| value.as_bytes().into()).collect())
272    }
273
274    fn append(
275        &mut self,
276        fields: Resource<Fields>,
277        name: FieldName,
278        value: FieldValue,
279    ) -> HeaderResult<()> {
280        let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
281        if self.ctx.is_forbidden_header(&name) {
282            return Err(HeaderError::Forbidden.into());
283        }
284        let value = parse_header_value(&name, value)?;
285        let fields = get_fields_mut(self.table, &fields)?;
286        let fields = fields.get_mut().ok_or(HeaderError::Immutable)?;
287        fields.append(name, value);
288        Ok(())
289    }
290
291    fn copy_all(
292        &mut self,
293        fields: Resource<Fields>,
294    ) -> wasmtime::Result<Vec<(FieldName, FieldValue)>> {
295        let fields = get_fields(self.table, &fields)?;
296        let fields = fields
297            .iter()
298            .map(|(name, value)| (name.as_str().into(), value.as_bytes().into()))
299            .collect();
300        Ok(fields)
301    }
302
303    fn clone(&mut self, fields: Resource<Fields>) -> wasmtime::Result<Resource<Fields>> {
304        let fields = get_fields(self.table, &fields)?;
305        push_fields(self.table, Fields::new_mutable(Arc::clone(fields)))
306    }
307
308    fn drop(&mut self, fields: Resource<Fields>) -> wasmtime::Result<()> {
309        delete_fields(self.table, fields)?;
310        Ok(())
311    }
312}
313
314impl HostRequestWithStore for WasiHttp {
315    fn new<T>(
316        mut store: Access<T, Self>,
317        headers: Resource<Headers>,
318        contents: Option<StreamReader<u8>>,
319        trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
320        options: Option<Resource<RequestOptions>>,
321    ) -> wasmtime::Result<(Resource<Request>, FutureReader<Result<(), ErrorCode>>)> {
322        let (result_tx, result_rx) = oneshot::channel();
323        let body = match contents
324            .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
325        {
326            Some(Ok(mut producer)) => Body::Host {
327                body: mem::take(&mut producer.body),
328                result_tx,
329            },
330            Some(Err(rx)) => Body::Guest {
331                contents_rx: Some(rx),
332                trailers_rx: trailers,
333                result_tx,
334            },
335            None => Body::Guest {
336                contents_rx: None,
337                trailers_rx: trailers,
338                result_tx,
339            },
340        };
341        let WasiHttpCtxView { table, .. } = store.get();
342        let headers = delete_fields(table, headers)?;
343        let options = options
344            .map(|options| delete_request_options(table, options))
345            .transpose()?;
346        let req = Request {
347            method: http::Method::GET,
348            scheme: None,
349            authority: None,
350            path_with_query: None,
351            headers: headers.into(),
352            options: options.map(Into::into),
353            body,
354        };
355        let req = table.push(req).context("failed to push request to table")?;
356        Ok((
357            req,
358            FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx)),
359        ))
360    }
361
362    fn consume_body<T>(
363        mut store: Access<T, Self>,
364        req: Resource<Request>,
365        fut: FutureReader<Result<(), ErrorCode>>,
366    ) -> wasmtime::Result<(
367        StreamReader<u8>,
368        FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
369    )> {
370        let getter = store.getter();
371        let Request { body, .. } = store
372            .get()
373            .table
374            .delete(req)
375            .context("failed to delete request from table")?;
376        Ok(body.consume(store, fut, getter))
377    }
378
379    fn drop<T>(mut store: Access<'_, T, Self>, req: Resource<Request>) -> wasmtime::Result<()> {
380        let Request { body, .. } = store
381            .get()
382            .table
383            .delete(req)
384            .context("failed to delete request from table")?;
385        body.drop(store);
386        Ok(())
387    }
388}
389
390impl HostRequest for WasiHttpCtxView<'_> {
391    fn get_method(&mut self, req: Resource<Request>) -> wasmtime::Result<Method> {
392        let Request { method, .. } = get_request(self.table, &req)?;
393        Ok(method.into())
394    }
395
396    fn set_method(
397        &mut self,
398        req: Resource<Request>,
399        method: Method,
400    ) -> wasmtime::Result<Result<(), ()>> {
401        let req = get_request_mut(self.table, &req)?;
402        let Ok(method) = method.try_into() else {
403            return Ok(Err(()));
404        };
405        req.method = method;
406        Ok(Ok(()))
407    }
408
409    fn get_path_with_query(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
410        let Request {
411            path_with_query, ..
412        } = get_request(self.table, &req)?;
413        Ok(path_with_query.as_ref().map(|pq| pq.as_str().into()))
414    }
415
416    fn set_path_with_query(
417        &mut self,
418        req: Resource<Request>,
419        path_with_query: Option<String>,
420    ) -> wasmtime::Result<Result<(), ()>> {
421        let req = get_request_mut(self.table, &req)?;
422        let Some(path_with_query) = path_with_query else {
423            req.path_with_query = None;
424            return Ok(Ok(()));
425        };
426        let Ok(path_with_query) = path_with_query.try_into() else {
427            return Ok(Err(()));
428        };
429        req.path_with_query = Some(path_with_query);
430        Ok(Ok(()))
431    }
432
433    fn get_scheme(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<Scheme>> {
434        let Request { scheme, .. } = get_request(self.table, &req)?;
435        Ok(scheme.as_ref().map(Into::into))
436    }
437
438    fn set_scheme(
439        &mut self,
440        req: Resource<Request>,
441        scheme: Option<Scheme>,
442    ) -> wasmtime::Result<Result<(), ()>> {
443        let req = get_request_mut(self.table, &req)?;
444        let Some(scheme) = scheme else {
445            req.scheme = None;
446            return Ok(Ok(()));
447        };
448        let Ok(scheme) = scheme.try_into() else {
449            return Ok(Err(()));
450        };
451        req.scheme = Some(scheme);
452        Ok(Ok(()))
453    }
454
455    fn get_authority(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
456        let Request { authority, .. } = get_request(self.table, &req)?;
457        Ok(authority.as_ref().map(|auth| auth.as_str().into()))
458    }
459
460    fn set_authority(
461        &mut self,
462        req: Resource<Request>,
463        authority: Option<String>,
464    ) -> wasmtime::Result<Result<(), ()>> {
465        let req = get_request_mut(self.table, &req)?;
466        let Some(authority) = authority else {
467            req.authority = None;
468            return Ok(Ok(()));
469        };
470        let has_port = authority.contains(':');
471        let Ok(authority) = http::uri::Authority::try_from(authority) else {
472            return Ok(Err(()));
473        };
474        if has_port && authority.port_u16().is_none() {
475            return Ok(Err(()));
476        }
477        req.authority = Some(authority);
478        Ok(Ok(()))
479    }
480
481    fn get_options(
482        &mut self,
483        req: Resource<Request>,
484    ) -> wasmtime::Result<Option<Resource<RequestOptions>>> {
485        let Request { options, .. } = get_request(self.table, &req)?;
486        if let Some(options) = options {
487            let options = push_request_options(
488                self.table,
489                RequestOptions::new_immutable(Arc::clone(options)),
490            )?;
491            Ok(Some(options))
492        } else {
493            Ok(None)
494        }
495    }
496
497    fn get_headers(&mut self, req: Resource<Request>) -> wasmtime::Result<Resource<Headers>> {
498        let Request { headers, .. } = get_request(self.table, &req)?;
499        push_fields(self.table, Fields::new_immutable(Arc::clone(headers)))
500    }
501}
502
503impl HostRequestOptions for WasiHttpCtxView<'_> {
504    fn new(&mut self) -> wasmtime::Result<Resource<RequestOptions>> {
505        push_request_options(self.table, RequestOptions::new_mutable_default())
506    }
507
508    fn get_connect_timeout(
509        &mut self,
510        opts: Resource<RequestOptions>,
511    ) -> wasmtime::Result<Option<Duration>> {
512        let opts = get_request_options(self.table, &opts)?;
513        let Some(connect_timeout) = opts.connect_timeout else {
514            return Ok(None);
515        };
516        let ns = connect_timeout.as_nanos();
517        let ns = ns
518            .try_into()
519            .context("connect timeout duration nanoseconds do not fit in u64")?;
520        Ok(Some(ns))
521    }
522
523    fn set_connect_timeout(
524        &mut self,
525        opts: Resource<RequestOptions>,
526        duration: Option<Duration>,
527    ) -> RequestOptionsResult<()> {
528        let opts = get_request_options_mut(self.table, &opts)?;
529        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
530        opts.connect_timeout = duration.map(core::time::Duration::from_nanos);
531        Ok(())
532    }
533
534    fn get_first_byte_timeout(
535        &mut self,
536        opts: Resource<RequestOptions>,
537    ) -> wasmtime::Result<Option<Duration>> {
538        let opts = get_request_options(self.table, &opts)?;
539        let Some(first_byte_timeout) = opts.first_byte_timeout else {
540            return Ok(None);
541        };
542        let ns = first_byte_timeout.as_nanos();
543        let ns = ns
544            .try_into()
545            .context("first byte timeout duration nanoseconds do not fit in u64")?;
546        Ok(Some(ns))
547    }
548
549    fn set_first_byte_timeout(
550        &mut self,
551        opts: Resource<RequestOptions>,
552        duration: Option<Duration>,
553    ) -> RequestOptionsResult<()> {
554        let opts = get_request_options_mut(self.table, &opts)?;
555        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
556        opts.first_byte_timeout = duration.map(core::time::Duration::from_nanos);
557        Ok(())
558    }
559
560    fn get_between_bytes_timeout(
561        &mut self,
562        opts: Resource<RequestOptions>,
563    ) -> wasmtime::Result<Option<Duration>> {
564        let opts = get_request_options(self.table, &opts)?;
565        let Some(between_bytes_timeout) = opts.between_bytes_timeout else {
566            return Ok(None);
567        };
568        let ns = between_bytes_timeout.as_nanos();
569        let ns = ns
570            .try_into()
571            .context("between bytes timeout duration nanoseconds do not fit in u64")?;
572        Ok(Some(ns))
573    }
574
575    fn set_between_bytes_timeout(
576        &mut self,
577        opts: Resource<RequestOptions>,
578        duration: Option<Duration>,
579    ) -> RequestOptionsResult<()> {
580        let opts = get_request_options_mut(self.table, &opts)?;
581        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
582        opts.between_bytes_timeout = duration.map(core::time::Duration::from_nanos);
583        Ok(())
584    }
585
586    fn clone(
587        &mut self,
588        opts: Resource<RequestOptions>,
589    ) -> wasmtime::Result<Resource<RequestOptions>> {
590        let opts = get_request_options(self.table, &opts)?;
591        push_request_options(self.table, RequestOptions::new_mutable(Arc::clone(opts)))
592    }
593
594    fn drop(&mut self, opts: Resource<RequestOptions>) -> wasmtime::Result<()> {
595        delete_request_options(self.table, opts)?;
596        Ok(())
597    }
598}
599
600impl HostResponseWithStore for WasiHttp {
601    fn new<T>(
602        mut store: Access<T, Self>,
603        headers: Resource<Headers>,
604        contents: Option<StreamReader<u8>>,
605        trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
606    ) -> wasmtime::Result<(Resource<Response>, FutureReader<Result<(), ErrorCode>>)> {
607        let (result_tx, result_rx) = oneshot::channel();
608        let body = match contents
609            .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
610        {
611            Some(Ok(mut producer)) => Body::Host {
612                body: mem::take(&mut producer.body),
613                result_tx,
614            },
615            Some(Err(rx)) => Body::Guest {
616                contents_rx: Some(rx),
617                trailers_rx: trailers,
618                result_tx,
619            },
620            None => Body::Guest {
621                contents_rx: None,
622                trailers_rx: trailers,
623                result_tx,
624            },
625        };
626        let WasiHttpCtxView { table, .. } = store.get();
627        let headers = delete_fields(table, headers)?;
628        let res = Response {
629            status: http::StatusCode::OK,
630            headers: headers.into(),
631            body,
632        };
633        let res = table
634            .push(res)
635            .context("failed to push response to table")?;
636        Ok((
637            res,
638            FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx)),
639        ))
640    }
641
642    fn consume_body<T>(
643        mut store: Access<T, Self>,
644        res: Resource<Response>,
645        fut: FutureReader<Result<(), ErrorCode>>,
646    ) -> wasmtime::Result<(
647        StreamReader<u8>,
648        FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
649    )> {
650        let getter = store.getter();
651        let Response { body, .. } = store
652            .get()
653            .table
654            .delete(res)
655            .context("failed to delete response from table")?;
656        Ok(body.consume(store, fut, getter))
657    }
658
659    fn drop<T>(mut store: Access<'_, T, Self>, res: Resource<Response>) -> wasmtime::Result<()> {
660        let Response { body, .. } = store
661            .get()
662            .table
663            .delete(res)
664            .context("failed to delete response from table")?;
665        body.drop(store);
666        Ok(())
667    }
668}
669
670impl HostResponse for WasiHttpCtxView<'_> {
671    fn get_status_code(&mut self, res: Resource<Response>) -> wasmtime::Result<StatusCode> {
672        let res = get_response(self.table, &res)?;
673        Ok(res.status.into())
674    }
675
676    fn set_status_code(
677        &mut self,
678        res: Resource<Response>,
679        status_code: StatusCode,
680    ) -> wasmtime::Result<Result<(), ()>> {
681        let res = get_response_mut(self.table, &res)?;
682        let Ok(status) = http::StatusCode::from_u16(status_code) else {
683            return Ok(Err(()));
684        };
685        res.status = status;
686        Ok(Ok(()))
687    }
688
689    fn get_headers(&mut self, res: Resource<Response>) -> wasmtime::Result<Resource<Headers>> {
690        let Response { headers, .. } = get_response(self.table, &res)?;
691        push_fields(self.table, Fields::new_immutable(Arc::clone(headers)))
692    }
693}
694
695impl Host for WasiHttpCtxView<'_> {
696    fn convert_error_code(&mut self, error: HttpError) -> wasmtime::Result<ErrorCode> {
697        error.downcast()
698    }
699
700    fn convert_header_error(
701        &mut self,
702        error: crate::p3::HeaderError,
703    ) -> wasmtime::Result<HeaderError> {
704        error.downcast()
705    }
706
707    fn convert_request_options_error(
708        &mut self,
709        error: crate::p3::RequestOptionsError,
710    ) -> wasmtime::Result<RequestOptionsError> {
711        error.downcast()
712    }
713}