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, Accessor, 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    async fn new<T>(
316        store: &Accessor<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        store.with(|mut store| {
323            let (result_tx, result_rx) = oneshot::channel();
324            let body = match contents
325                .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
326            {
327                Some(Ok(mut producer)) => Body::Host {
328                    body: mem::take(&mut producer.body),
329                    result_tx,
330                },
331                Some(Err(rx)) => Body::Guest {
332                    contents_rx: Some(rx),
333                    trailers_rx: trailers,
334                    result_tx,
335                },
336                None => Body::Guest {
337                    contents_rx: None,
338                    trailers_rx: trailers,
339                    result_tx,
340                },
341            };
342            let WasiHttpCtxView { table, .. } = store.get();
343            let headers = delete_fields(table, headers)?;
344            let options = options
345                .map(|options| delete_request_options(table, options))
346                .transpose()?;
347            let req = Request {
348                method: http::Method::GET,
349                scheme: None,
350                authority: None,
351                path_with_query: None,
352                headers: headers.into(),
353                options: options.map(Into::into),
354                body,
355            };
356            let req = table.push(req).context("failed to push request to table")?;
357            Ok((
358                req,
359                FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx)),
360            ))
361        })
362    }
363
364    async fn consume_body<T>(
365        store: &Accessor<T, Self>,
366        req: Resource<Request>,
367        fut: FutureReader<Result<(), ErrorCode>>,
368    ) -> wasmtime::Result<(
369        StreamReader<u8>,
370        FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
371    )> {
372        let getter = store.getter();
373        store.with(|mut store| {
374            let Request { body, .. } = store
375                .get()
376                .table
377                .delete(req)
378                .context("failed to delete request from table")?;
379            Ok(body.consume(store, fut, getter))
380        })
381    }
382
383    fn drop<T>(mut store: Access<'_, T, Self>, req: Resource<Request>) -> wasmtime::Result<()> {
384        let Request { body, .. } = store
385            .get()
386            .table
387            .delete(req)
388            .context("failed to delete request from table")?;
389        body.drop(store);
390        Ok(())
391    }
392}
393
394impl HostRequest for WasiHttpCtxView<'_> {
395    fn get_method(&mut self, req: Resource<Request>) -> wasmtime::Result<Method> {
396        let Request { method, .. } = get_request(self.table, &req)?;
397        Ok(method.into())
398    }
399
400    fn set_method(
401        &mut self,
402        req: Resource<Request>,
403        method: Method,
404    ) -> wasmtime::Result<Result<(), ()>> {
405        let req = get_request_mut(self.table, &req)?;
406        let Ok(method) = method.try_into() else {
407            return Ok(Err(()));
408        };
409        req.method = method;
410        Ok(Ok(()))
411    }
412
413    fn get_path_with_query(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
414        let Request {
415            path_with_query, ..
416        } = get_request(self.table, &req)?;
417        Ok(path_with_query.as_ref().map(|pq| pq.as_str().into()))
418    }
419
420    fn set_path_with_query(
421        &mut self,
422        req: Resource<Request>,
423        path_with_query: Option<String>,
424    ) -> wasmtime::Result<Result<(), ()>> {
425        let req = get_request_mut(self.table, &req)?;
426        let Some(path_with_query) = path_with_query else {
427            req.path_with_query = None;
428            return Ok(Ok(()));
429        };
430        let Ok(path_with_query) = path_with_query.try_into() else {
431            return Ok(Err(()));
432        };
433        req.path_with_query = Some(path_with_query);
434        Ok(Ok(()))
435    }
436
437    fn get_scheme(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<Scheme>> {
438        let Request { scheme, .. } = get_request(self.table, &req)?;
439        Ok(scheme.as_ref().map(Into::into))
440    }
441
442    fn set_scheme(
443        &mut self,
444        req: Resource<Request>,
445        scheme: Option<Scheme>,
446    ) -> wasmtime::Result<Result<(), ()>> {
447        let req = get_request_mut(self.table, &req)?;
448        let Some(scheme) = scheme else {
449            req.scheme = None;
450            return Ok(Ok(()));
451        };
452        let Ok(scheme) = scheme.try_into() else {
453            return Ok(Err(()));
454        };
455        req.scheme = Some(scheme);
456        Ok(Ok(()))
457    }
458
459    fn get_authority(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
460        let Request { authority, .. } = get_request(self.table, &req)?;
461        Ok(authority.as_ref().map(|auth| auth.as_str().into()))
462    }
463
464    fn set_authority(
465        &mut self,
466        req: Resource<Request>,
467        authority: Option<String>,
468    ) -> wasmtime::Result<Result<(), ()>> {
469        let req = get_request_mut(self.table, &req)?;
470        let Some(authority) = authority else {
471            req.authority = None;
472            return Ok(Ok(()));
473        };
474        let has_port = authority.contains(':');
475        let Ok(authority) = http::uri::Authority::try_from(authority) else {
476            return Ok(Err(()));
477        };
478        if has_port && authority.port_u16().is_none() {
479            return Ok(Err(()));
480        }
481        req.authority = Some(authority);
482        Ok(Ok(()))
483    }
484
485    fn get_options(
486        &mut self,
487        req: Resource<Request>,
488    ) -> wasmtime::Result<Option<Resource<RequestOptions>>> {
489        let Request { options, .. } = get_request(self.table, &req)?;
490        if let Some(options) = options {
491            let options = push_request_options(
492                self.table,
493                RequestOptions::new_immutable(Arc::clone(options)),
494            )?;
495            Ok(Some(options))
496        } else {
497            Ok(None)
498        }
499    }
500
501    fn get_headers(&mut self, req: Resource<Request>) -> wasmtime::Result<Resource<Headers>> {
502        let Request { headers, .. } = get_request(self.table, &req)?;
503        push_fields(self.table, Fields::new_immutable(Arc::clone(headers)))
504    }
505}
506
507impl HostRequestOptions for WasiHttpCtxView<'_> {
508    fn new(&mut self) -> wasmtime::Result<Resource<RequestOptions>> {
509        push_request_options(self.table, RequestOptions::new_mutable_default())
510    }
511
512    fn get_connect_timeout(
513        &mut self,
514        opts: Resource<RequestOptions>,
515    ) -> wasmtime::Result<Option<Duration>> {
516        let opts = get_request_options(self.table, &opts)?;
517        let Some(connect_timeout) = opts.connect_timeout else {
518            return Ok(None);
519        };
520        let ns = connect_timeout.as_nanos();
521        let ns = ns
522            .try_into()
523            .context("connect timeout duration nanoseconds do not fit in u64")?;
524        Ok(Some(ns))
525    }
526
527    fn set_connect_timeout(
528        &mut self,
529        opts: Resource<RequestOptions>,
530        duration: Option<Duration>,
531    ) -> RequestOptionsResult<()> {
532        let opts = get_request_options_mut(self.table, &opts)?;
533        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
534        opts.connect_timeout = duration.map(core::time::Duration::from_nanos);
535        Ok(())
536    }
537
538    fn get_first_byte_timeout(
539        &mut self,
540        opts: Resource<RequestOptions>,
541    ) -> wasmtime::Result<Option<Duration>> {
542        let opts = get_request_options(self.table, &opts)?;
543        let Some(first_byte_timeout) = opts.first_byte_timeout else {
544            return Ok(None);
545        };
546        let ns = first_byte_timeout.as_nanos();
547        let ns = ns
548            .try_into()
549            .context("first byte timeout duration nanoseconds do not fit in u64")?;
550        Ok(Some(ns))
551    }
552
553    fn set_first_byte_timeout(
554        &mut self,
555        opts: Resource<RequestOptions>,
556        duration: Option<Duration>,
557    ) -> RequestOptionsResult<()> {
558        let opts = get_request_options_mut(self.table, &opts)?;
559        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
560        opts.first_byte_timeout = duration.map(core::time::Duration::from_nanos);
561        Ok(())
562    }
563
564    fn get_between_bytes_timeout(
565        &mut self,
566        opts: Resource<RequestOptions>,
567    ) -> wasmtime::Result<Option<Duration>> {
568        let opts = get_request_options(self.table, &opts)?;
569        let Some(between_bytes_timeout) = opts.between_bytes_timeout else {
570            return Ok(None);
571        };
572        let ns = between_bytes_timeout.as_nanos();
573        let ns = ns
574            .try_into()
575            .context("between bytes timeout duration nanoseconds do not fit in u64")?;
576        Ok(Some(ns))
577    }
578
579    fn set_between_bytes_timeout(
580        &mut self,
581        opts: Resource<RequestOptions>,
582        duration: Option<Duration>,
583    ) -> RequestOptionsResult<()> {
584        let opts = get_request_options_mut(self.table, &opts)?;
585        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
586        opts.between_bytes_timeout = duration.map(core::time::Duration::from_nanos);
587        Ok(())
588    }
589
590    fn clone(
591        &mut self,
592        opts: Resource<RequestOptions>,
593    ) -> wasmtime::Result<Resource<RequestOptions>> {
594        let opts = get_request_options(self.table, &opts)?;
595        push_request_options(self.table, RequestOptions::new_mutable(Arc::clone(opts)))
596    }
597
598    fn drop(&mut self, opts: Resource<RequestOptions>) -> wasmtime::Result<()> {
599        delete_request_options(self.table, opts)?;
600        Ok(())
601    }
602}
603
604impl HostResponseWithStore for WasiHttp {
605    async fn new<T>(
606        store: &Accessor<T, Self>,
607        headers: Resource<Headers>,
608        contents: Option<StreamReader<u8>>,
609        trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
610    ) -> wasmtime::Result<(Resource<Response>, FutureReader<Result<(), ErrorCode>>)> {
611        store.with(|mut store| {
612            let (result_tx, result_rx) = oneshot::channel();
613            let body = match contents
614                .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
615            {
616                Some(Ok(mut producer)) => Body::Host {
617                    body: mem::take(&mut producer.body),
618                    result_tx,
619                },
620                Some(Err(rx)) => Body::Guest {
621                    contents_rx: Some(rx),
622                    trailers_rx: trailers,
623                    result_tx,
624                },
625                None => Body::Guest {
626                    contents_rx: None,
627                    trailers_rx: trailers,
628                    result_tx,
629                },
630            };
631            let WasiHttpCtxView { table, .. } = store.get();
632            let headers = delete_fields(table, headers)?;
633            let res = Response {
634                status: http::StatusCode::OK,
635                headers: headers.into(),
636                body,
637            };
638            let res = table
639                .push(res)
640                .context("failed to push response to table")?;
641            Ok((
642                res,
643                FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx)),
644            ))
645        })
646    }
647
648    async fn consume_body<T>(
649        store: &Accessor<T, Self>,
650        res: Resource<Response>,
651        fut: FutureReader<Result<(), ErrorCode>>,
652    ) -> wasmtime::Result<(
653        StreamReader<u8>,
654        FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
655    )> {
656        let getter = store.getter();
657        store.with(|mut store| {
658            let Response { body, .. } = store
659                .get()
660                .table
661                .delete(res)
662                .context("failed to delete response from table")?;
663            Ok(body.consume(store, fut, getter))
664        })
665    }
666
667    fn drop<T>(mut store: Access<'_, T, Self>, res: Resource<Response>) -> wasmtime::Result<()> {
668        let Response { body, .. } = store
669            .get()
670            .table
671            .delete(res)
672            .context("failed to delete response from table")?;
673        body.drop(store);
674        Ok(())
675    }
676}
677
678impl HostResponse for WasiHttpCtxView<'_> {
679    fn get_status_code(&mut self, res: Resource<Response>) -> wasmtime::Result<StatusCode> {
680        let res = get_response(self.table, &res)?;
681        Ok(res.status.into())
682    }
683
684    fn set_status_code(
685        &mut self,
686        res: Resource<Response>,
687        status_code: StatusCode,
688    ) -> wasmtime::Result<Result<(), ()>> {
689        let res = get_response_mut(self.table, &res)?;
690        let Ok(status) = http::StatusCode::from_u16(status_code) else {
691            return Ok(Err(()));
692        };
693        res.status = status;
694        Ok(Ok(()))
695    }
696
697    fn get_headers(&mut self, res: Resource<Response>) -> wasmtime::Result<Resource<Headers>> {
698        let Response { headers, .. } = get_response(self.table, &res)?;
699        push_fields(self.table, Fields::new_immutable(Arc::clone(headers)))
700    }
701}
702
703impl Host for WasiHttpCtxView<'_> {
704    fn convert_error_code(&mut self, error: HttpError) -> wasmtime::Result<ErrorCode> {
705        error.downcast()
706    }
707
708    fn convert_header_error(
709        &mut self,
710        error: crate::p3::HeaderError,
711    ) -> wasmtime::Result<HeaderError> {
712        error.downcast()
713    }
714
715    fn convert_request_options_error(
716        &mut self,
717        error: crate::p3::RequestOptionsError,
718    ) -> wasmtime::Result<RequestOptionsError> {
719        error.downcast()
720    }
721}