Skip to main content

wasmtime_wasi_http/p3/host/
types.rs

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