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 let res = poll_future(cx, fut.as_mut(), finish);
165 *self = Self::Future(fut);
166 res.map(Ok)
167 }
168 Some(Err(..)) => {
169 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}