Skip to main content

wasmtime_wasi_http/p2/
types_impl.rs

1//! Implementation for the `wasi:http/types` interface.
2
3use crate::p2::bindings::http::types::{self, Method, Scheme, StatusCode, Trailers};
4use crate::p2::body::{HostFutureTrailers, HostIncomingBody, HostOutgoingBody, StreamContext};
5use crate::p2::types::{
6    HostFutureIncomingResponse, HostIncomingRequest, HostIncomingResponse, HostOutgoingRequest,
7    HostOutgoingResponse, HostResponseOutparam,
8};
9use crate::p2::{HeaderError, HeaderResult, HttpError, HttpResult};
10use crate::{FieldMap, WasiHttpCtxView, get_content_length};
11use http::HeaderName;
12use std::str::FromStr;
13use wasmtime::component::Resource;
14use wasmtime::{error::Context as _, format_err};
15use wasmtime_wasi::p2::{DynInputStream, DynOutputStream, DynPollable};
16
17impl types::Host for WasiHttpCtxView<'_> {
18    fn convert_error_code(&mut self, err: HttpError) -> wasmtime::Result<types::ErrorCode> {
19        err.downcast()
20    }
21
22    fn convert_header_error(&mut self, err: HeaderError) -> wasmtime::Result<types::HeaderError> {
23        err.downcast()
24    }
25
26    fn http_error_code(
27        &mut self,
28        err: wasmtime::component::Resource<types::IoError>,
29    ) -> wasmtime::Result<Option<types::ErrorCode>> {
30        let e = self.table.get(&err)?;
31        if let Some(e) = e.downcast_ref::<crate::Error>() {
32            return Ok(Some(self.hooks.error_to_p2(e)));
33        }
34        if let Some(e) = e.downcast_ref::<std::sync::Arc<crate::Error>>() {
35            return Ok(Some(self.hooks.error_to_p2(e)));
36        }
37        Ok(e.downcast_ref::<types::ErrorCode>().cloned())
38    }
39}
40
41impl types::HostFields for WasiHttpCtxView<'_> {
42    fn new(&mut self) -> wasmtime::Result<Resource<FieldMap>> {
43        let limit = self.ctx.field_size_limit;
44        let id = self
45            .table
46            .push(FieldMap::new_mutable(limit))
47            .context("[new_fields] pushing fields")?;
48
49        Ok(id)
50    }
51
52    fn from_list(&mut self, entries: Vec<(String, Vec<u8>)>) -> HeaderResult<Resource<FieldMap>> {
53        let mut fields = FieldMap::new_mutable(self.ctx.field_size_limit);
54
55        for (header, value) in entries {
56            fields.append(self.hooks, header, value)?;
57        }
58
59        Ok(self.table.push(fields)?)
60    }
61
62    fn drop(&mut self, fields: Resource<FieldMap>) -> wasmtime::Result<()> {
63        self.table
64            .delete(fields)
65            .context("[drop_fields] deleting fields")?;
66        Ok(())
67    }
68
69    fn get(&mut self, fields: Resource<FieldMap>, name: String) -> wasmtime::Result<Vec<Vec<u8>>> {
70        let fields = self.table.get(&fields)?;
71
72        let header = match HeaderName::from_bytes(name.as_bytes()) {
73            Ok(header) => header,
74            Err(_) => return Ok(vec![]),
75        };
76
77        if !fields.contains_key(&header) {
78            return Ok(vec![]);
79        }
80
81        let res = fields
82            .get_all(&header)
83            .into_iter()
84            .map(|val| val.as_bytes().to_owned())
85            .collect();
86        Ok(res)
87    }
88
89    fn has(&mut self, fields: Resource<FieldMap>, name: String) -> wasmtime::Result<bool> {
90        let fields = self.table.get(&fields)?;
91
92        match HeaderName::from_bytes(name.as_bytes()) {
93            Ok(header) => Ok(fields.contains_key(&header)),
94            Err(_) => Ok(false),
95        }
96    }
97
98    fn set(
99        &mut self,
100        fields: Resource<FieldMap>,
101        name: String,
102        values: Vec<Vec<u8>>,
103    ) -> HeaderResult<()> {
104        let fields = self.table.get_mut(&fields)?;
105        fields.set(self.hooks, name, values)?;
106        Ok(())
107    }
108
109    fn delete(&mut self, fields: Resource<FieldMap>, name: String) -> HeaderResult<()> {
110        let fields = self.table.get_mut(&fields)?;
111        fields.remove_all(self.hooks, name)?;
112        Ok(())
113    }
114
115    fn append(
116        &mut self,
117        fields: Resource<FieldMap>,
118        name: String,
119        value: Vec<u8>,
120    ) -> HeaderResult<()> {
121        let fields = self.table.get_mut(&fields)?;
122        fields.append(self.hooks, name, value)?;
123        Ok(())
124    }
125
126    fn entries(&mut self, fields: Resource<FieldMap>) -> wasmtime::Result<Vec<(String, Vec<u8>)>> {
127        Ok(self
128            .table
129            .get(&fields)?
130            .iter()
131            .map(|(name, value)| (name.as_str().to_owned(), value.as_bytes().to_owned()))
132            .collect())
133    }
134
135    fn clone(&mut self, fields: Resource<FieldMap>) -> wasmtime::Result<Resource<FieldMap>> {
136        let mut fields = self.table.get(&fields)?.clone();
137        fields.set_mutable(self.ctx.field_size_limit);
138        let id = self.table.push(fields)?;
139        Ok(id)
140    }
141}
142
143impl types::HostIncomingRequest for WasiHttpCtxView<'_> {
144    fn method(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<Method> {
145        let method = self.table.get(&id)?.method.clone();
146        Ok(method.into())
147    }
148    fn path_with_query(
149        &mut self,
150        id: Resource<HostIncomingRequest>,
151    ) -> wasmtime::Result<Option<String>> {
152        let req = self.table.get(&id)?;
153        Ok(req
154            .uri
155            .path_and_query()
156            .map(|path_and_query| path_and_query.as_str().to_owned()))
157    }
158    fn scheme(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<Option<Scheme>> {
159        let req = self.table.get(&id)?;
160        Ok(Some(req.scheme.clone()))
161    }
162    fn authority(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<Option<String>> {
163        let req = self.table.get(&id)?;
164        Ok(Some(req.authority.clone()))
165    }
166
167    fn headers(
168        &mut self,
169        id: Resource<HostIncomingRequest>,
170    ) -> wasmtime::Result<Resource<FieldMap>> {
171        // `incoming-request.headers` is a child resource: the parent must not be
172        // dropped while this handle is alive (`wit/deps/http.wit:267-269` says that
173        // "dropping this `incoming-request` before all children are dropped will trap").
174        // Register the parent/child relationship so the resource table enforces it,
175        // matching `incoming-body.stream` and `outgoing-body.write` below.
176        let headers = self.table.get(&id)?.headers.clone();
177        Ok(self.table.push_child(headers, &id)?)
178    }
179
180    fn consume(
181        &mut self,
182        id: Resource<HostIncomingRequest>,
183    ) -> wasmtime::Result<Result<Resource<HostIncomingBody>, ()>> {
184        let req = self.table.get_mut(&id)?;
185        match req.body.take() {
186            Some(body) => {
187                let id = self.table.push(body)?;
188                Ok(Ok(id))
189            }
190
191            None => Ok(Err(())),
192        }
193    }
194
195    fn drop(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<()> {
196        let _ = self.table.delete(id)?;
197        Ok(())
198    }
199}
200
201impl types::HostOutgoingRequest for WasiHttpCtxView<'_> {
202    fn new(
203        &mut self,
204        headers: Resource<FieldMap>,
205    ) -> wasmtime::Result<Resource<HostOutgoingRequest>> {
206        let mut headers = self.table.delete(headers)?;
207        headers.set_immutable();
208
209        self.table
210            .push(HostOutgoingRequest {
211                path_with_query: None,
212                authority: None,
213                method: types::Method::Get,
214                headers,
215                scheme: None,
216                body: None,
217            })
218            .context("[new_outgoing_request] pushing request")
219    }
220
221    fn body(
222        &mut self,
223        request: Resource<HostOutgoingRequest>,
224    ) -> wasmtime::Result<Result<Resource<HostOutgoingBody>, ()>> {
225        let buffer_chunks = self.hooks.p2_outgoing_body_buffer_chunks();
226        let chunk_size = self.hooks.p2_outgoing_body_chunk_size();
227        let req = self
228            .table
229            .get_mut(&request)
230            .context("[outgoing_request_write] getting request")?;
231
232        if req.body.is_some() {
233            return Ok(Err(()));
234        }
235
236        let size = match get_content_length(&req.headers) {
237            Ok(size) => size,
238            Err(..) => return Ok(Err(())),
239        };
240
241        let (host_body, hyper_body) =
242            HostOutgoingBody::new(StreamContext::Request, size, buffer_chunks, chunk_size);
243
244        req.body = Some(hyper_body);
245
246        // The output stream will necessarily outlive the request, because we could be still
247        // writing to the stream after `outgoing-handler.handle` is called.
248        let outgoing_body = self.table.push(host_body)?;
249
250        Ok(Ok(outgoing_body))
251    }
252
253    fn drop(&mut self, request: Resource<HostOutgoingRequest>) -> wasmtime::Result<()> {
254        let _ = self.table.delete(request)?;
255        Ok(())
256    }
257
258    fn method(
259        &mut self,
260        request: wasmtime::component::Resource<types::OutgoingRequest>,
261    ) -> wasmtime::Result<Method> {
262        Ok(self.table.get(&request)?.method.clone())
263    }
264
265    fn set_method(
266        &mut self,
267        request: wasmtime::component::Resource<types::OutgoingRequest>,
268        method: Method,
269    ) -> wasmtime::Result<Result<(), ()>> {
270        let req = self.table.get_mut(&request)?;
271
272        if let Method::Other(s) = &method {
273            if let Err(_) = http::Method::from_str(s) {
274                return Ok(Err(()));
275            }
276        }
277
278        req.method = method;
279
280        Ok(Ok(()))
281    }
282
283    fn path_with_query(
284        &mut self,
285        request: wasmtime::component::Resource<types::OutgoingRequest>,
286    ) -> wasmtime::Result<Option<String>> {
287        Ok(self.table.get(&request)?.path_with_query.clone())
288    }
289
290    fn set_path_with_query(
291        &mut self,
292        request: wasmtime::component::Resource<types::OutgoingRequest>,
293        path_with_query: Option<String>,
294    ) -> wasmtime::Result<Result<(), ()>> {
295        let req = self.table.get_mut(&request)?;
296
297        if let Some(s) = path_with_query.as_ref() {
298            if crate::parse_path_with_query(s).is_none() {
299                return Ok(Err(()));
300            }
301        }
302
303        req.path_with_query = path_with_query;
304
305        Ok(Ok(()))
306    }
307
308    fn scheme(
309        &mut self,
310        request: wasmtime::component::Resource<types::OutgoingRequest>,
311    ) -> wasmtime::Result<Option<Scheme>> {
312        Ok(self.table.get(&request)?.scheme.clone())
313    }
314
315    fn set_scheme(
316        &mut self,
317        request: wasmtime::component::Resource<types::OutgoingRequest>,
318        scheme: Option<Scheme>,
319    ) -> wasmtime::Result<Result<(), ()>> {
320        let req = self.table.get_mut(&request)?;
321
322        if let Some(types::Scheme::Other(s)) = scheme.as_ref() {
323            if let Err(_) = http::uri::Scheme::from_str(s.as_str()) {
324                return Ok(Err(()));
325            }
326        }
327
328        req.scheme = scheme;
329
330        Ok(Ok(()))
331    }
332
333    fn authority(
334        &mut self,
335        request: wasmtime::component::Resource<types::OutgoingRequest>,
336    ) -> wasmtime::Result<Option<String>> {
337        Ok(self.table.get(&request)?.authority.clone())
338    }
339
340    fn set_authority(
341        &mut self,
342        request: wasmtime::component::Resource<types::OutgoingRequest>,
343        authority: Option<String>,
344    ) -> wasmtime::Result<Result<(), ()>> {
345        let req = self.table.get_mut(&request)?;
346
347        // Match p3: reject empty / non-numeric / out-of-range ports that
348        // `http::uri::Authority` alone would accept (see crate::parse_authority).
349        if let Some(s) = authority {
350            let Ok(parsed) = crate::parse_authority(s) else {
351                return Ok(Err(()));
352            };
353            req.authority = Some(parsed.as_str().into());
354        } else {
355            req.authority = None;
356        }
357
358        Ok(Ok(()))
359    }
360
361    fn headers(
362        &mut self,
363        request: wasmtime::component::Resource<types::OutgoingRequest>,
364    ) -> wasmtime::Result<wasmtime::component::Resource<FieldMap>> {
365        let req = self.table.get(&request)?;
366        let id = self.table.push(req.headers.clone())?;
367        Ok(id)
368    }
369}
370
371impl types::HostResponseOutparam for WasiHttpCtxView<'_> {
372    fn drop(&mut self, id: Resource<HostResponseOutparam>) -> wasmtime::Result<()> {
373        let _ = self.table.delete(id)?;
374        Ok(())
375    }
376    fn set(
377        &mut self,
378        id: Resource<HostResponseOutparam>,
379        resp: Result<Resource<HostOutgoingResponse>, types::ErrorCode>,
380    ) -> wasmtime::Result<()> {
381        let val = match resp {
382            Ok(resp) => Ok(self.table.delete(resp)?.try_into()?),
383            Err(e) => Err(e),
384        };
385
386        let resp = self.table.delete(id)?;
387        (resp.send)(val);
388        Ok(())
389    }
390
391    fn send_informational(
392        &mut self,
393        _id: Resource<HostResponseOutparam>,
394        _status: u16,
395        _headers: Resource<FieldMap>,
396    ) -> HttpResult<()> {
397        Err(HttpError::trap(format_err!("not implemented")))
398    }
399}
400
401impl types::HostIncomingResponse for WasiHttpCtxView<'_> {
402    fn drop(&mut self, response: Resource<HostIncomingResponse>) -> wasmtime::Result<()> {
403        let _ = self
404            .table
405            .delete(response)
406            .context("[drop_incoming_response] deleting response")?;
407        Ok(())
408    }
409
410    fn status(&mut self, response: Resource<HostIncomingResponse>) -> wasmtime::Result<StatusCode> {
411        let r = self
412            .table
413            .get(&response)
414            .context("[incoming_response_status] getting response")?;
415        Ok(r.status)
416    }
417
418    fn headers(
419        &mut self,
420        response: Resource<HostIncomingResponse>,
421    ) -> wasmtime::Result<Resource<FieldMap>> {
422        let resp = self.table.get(&response)?;
423        let id = self.table.push(resp.headers.clone())?;
424        Ok(id)
425    }
426
427    fn consume(
428        &mut self,
429        response: Resource<HostIncomingResponse>,
430    ) -> wasmtime::Result<Result<Resource<HostIncomingBody>, ()>> {
431        let r = self
432            .table
433            .get_mut(&response)
434            .context("[incoming_response_consume] getting response")?;
435
436        match r.body.take() {
437            Some(body) => {
438                let id = self.table.push(body)?;
439                Ok(Ok(id))
440            }
441
442            None => Ok(Err(())),
443        }
444    }
445}
446
447impl types::HostFutureTrailers for WasiHttpCtxView<'_> {
448    fn drop(&mut self, id: Resource<HostFutureTrailers>) -> wasmtime::Result<()> {
449        let _ = self
450            .table
451            .delete(id)
452            .context("[drop future-trailers] deleting future-trailers")?;
453        Ok(())
454    }
455
456    fn subscribe(
457        &mut self,
458        index: Resource<HostFutureTrailers>,
459    ) -> wasmtime::Result<Resource<DynPollable>> {
460        wasmtime_wasi::p2::subscribe(self.table, index)
461    }
462
463    fn get(
464        &mut self,
465        id: Resource<HostFutureTrailers>,
466    ) -> wasmtime::Result<Option<Result<Result<Option<Resource<Trailers>>, types::ErrorCode>, ()>>>
467    {
468        let trailers = self.table.get_mut(&id)?;
469        match trailers {
470            HostFutureTrailers::Waiting { .. } => return Ok(None),
471            HostFutureTrailers::Consumed => return Ok(Some(Err(()))),
472            HostFutureTrailers::Done(_) => {}
473        };
474
475        let res = match std::mem::replace(trailers, HostFutureTrailers::Consumed) {
476            HostFutureTrailers::Done(res) => res,
477            _ => unreachable!(),
478        };
479
480        let fields = match res {
481            Ok(Some(fields)) => fields,
482            Ok(None) => return Ok(Some(Ok(Ok(None)))),
483            Err(e) => {
484                let e = self.hooks.error_to_p2(&e);
485                return Ok(Some(Ok(Err(e))));
486            }
487        };
488
489        let ts = self
490            .table
491            .push(FieldMap::new_immutable(self.hooks, fields))?;
492
493        Ok(Some(Ok(Ok(Some(ts)))))
494    }
495}
496
497impl types::HostIncomingBody for WasiHttpCtxView<'_> {
498    fn stream(
499        &mut self,
500        id: Resource<HostIncomingBody>,
501    ) -> wasmtime::Result<Result<Resource<DynInputStream>, ()>> {
502        let body = self.table.get_mut(&id)?;
503
504        if let Some(stream) = body.take_stream() {
505            let stream: DynInputStream = Box::new(stream);
506            let stream = self.table.push_child(stream, &id)?;
507            return Ok(Ok(stream));
508        }
509
510        Ok(Err(()))
511    }
512
513    fn finish(
514        &mut self,
515        id: Resource<HostIncomingBody>,
516    ) -> wasmtime::Result<Resource<HostFutureTrailers>> {
517        let body = self.table.delete(id)?;
518        let trailers = self.table.push(body.into_future_trailers())?;
519        Ok(trailers)
520    }
521
522    fn drop(&mut self, id: Resource<HostIncomingBody>) -> wasmtime::Result<()> {
523        let _ = self.table.delete(id)?;
524        Ok(())
525    }
526}
527
528impl types::HostOutgoingResponse for WasiHttpCtxView<'_> {
529    fn new(
530        &mut self,
531        headers: Resource<FieldMap>,
532    ) -> wasmtime::Result<Resource<HostOutgoingResponse>> {
533        let mut fields = self.table.delete(headers)?;
534        fields.set_immutable();
535
536        let id = self.table.push(HostOutgoingResponse {
537            status: http::StatusCode::OK,
538            headers: fields,
539            body: None,
540        })?;
541
542        Ok(id)
543    }
544
545    fn body(
546        &mut self,
547        id: Resource<HostOutgoingResponse>,
548    ) -> wasmtime::Result<Result<Resource<HostOutgoingBody>, ()>> {
549        let buffer_chunks = self.hooks.p2_outgoing_body_buffer_chunks();
550        let chunk_size = self.hooks.p2_outgoing_body_chunk_size();
551        let resp = self.table.get_mut(&id)?;
552
553        if resp.body.is_some() {
554            return Ok(Err(()));
555        }
556
557        let size = match get_content_length(&resp.headers) {
558            Ok(size) => size,
559            Err(..) => return Ok(Err(())),
560        };
561
562        let (host, body) =
563            HostOutgoingBody::new(StreamContext::Response, size, buffer_chunks, chunk_size);
564
565        resp.body.replace(body);
566
567        let id = self.table.push(host)?;
568
569        Ok(Ok(id))
570    }
571
572    fn status_code(
573        &mut self,
574        id: Resource<HostOutgoingResponse>,
575    ) -> wasmtime::Result<types::StatusCode> {
576        Ok(self.table.get(&id)?.status.into())
577    }
578
579    fn set_status_code(
580        &mut self,
581        id: Resource<HostOutgoingResponse>,
582        status: types::StatusCode,
583    ) -> wasmtime::Result<Result<(), ()>> {
584        let resp = self.table.get_mut(&id)?;
585
586        match http::StatusCode::from_u16(status) {
587            Ok(parsed) if matches!(status, 100..=599) => resp.status = parsed,
588            _ => return Ok(Err(())),
589        };
590
591        Ok(Ok(()))
592    }
593
594    fn headers(
595        &mut self,
596        id: Resource<HostOutgoingResponse>,
597    ) -> wasmtime::Result<Resource<FieldMap>> {
598        let resp = self.table.get(&id)?;
599        Ok(self.table.push(resp.headers.clone())?)
600    }
601
602    fn drop(&mut self, id: Resource<HostOutgoingResponse>) -> wasmtime::Result<()> {
603        let _ = self.table.delete(id)?;
604        Ok(())
605    }
606}
607
608impl types::HostFutureIncomingResponse for WasiHttpCtxView<'_> {
609    fn drop(&mut self, id: Resource<HostFutureIncomingResponse>) -> wasmtime::Result<()> {
610        let _ = self.table.delete(id)?;
611        Ok(())
612    }
613
614    fn get(
615        &mut self,
616        id: Resource<HostFutureIncomingResponse>,
617    ) -> wasmtime::Result<
618        Option<Result<Result<Resource<HostIncomingResponse>, types::ErrorCode>, ()>>,
619    > {
620        let resp = self.table.get_mut(&id)?;
621
622        match resp {
623            HostFutureIncomingResponse::Pending(_) => return Ok(None),
624            HostFutureIncomingResponse::Consumed => return Ok(Some(Err(()))),
625            HostFutureIncomingResponse::Ready(_) => {}
626        }
627
628        let (resp, io) =
629            match std::mem::replace(resp, HostFutureIncomingResponse::Consumed).unwrap_ready() {
630                Ok(pair) => pair,
631                Err(e) => {
632                    let e = self.hooks.error_to_p2(&e);
633                    return Ok(Some(Ok(Err(e))));
634                }
635            };
636
637        let (parts, body) = resp.into_parts();
638        let headers = FieldMap::new_immutable(self.hooks, parts.headers);
639
640        let resp = self.table.push(HostIncomingResponse {
641            status: parts.status.as_u16(),
642            headers,
643            body: Some({
644                let mut body = HostIncomingBody::new(body);
645                body.retain_worker(io);
646                body
647            }),
648        })?;
649
650        Ok(Some(Ok(Ok(resp))))
651    }
652
653    fn subscribe(
654        &mut self,
655        id: Resource<HostFutureIncomingResponse>,
656    ) -> wasmtime::Result<Resource<DynPollable>> {
657        wasmtime_wasi::p2::subscribe(self.table, id)
658    }
659}
660
661impl types::HostOutgoingBody for WasiHttpCtxView<'_> {
662    fn write(
663        &mut self,
664        id: Resource<HostOutgoingBody>,
665    ) -> wasmtime::Result<Result<Resource<DynOutputStream>, ()>> {
666        let body = self.table.get_mut(&id)?;
667        if let Some(stream) = body.take_output_stream() {
668            let id = self.table.push_child(stream, &id)?;
669            Ok(Ok(id))
670        } else {
671            Ok(Err(()))
672        }
673    }
674
675    fn finish(
676        &mut self,
677        id: Resource<HostOutgoingBody>,
678        ts: Option<Resource<Trailers>>,
679    ) -> HttpResult<()> {
680        let body = self.table.delete(id)?;
681
682        let ts = if let Some(ts) = ts {
683            Some(self.table.delete(ts)?)
684        } else {
685            None
686        };
687
688        body.finish(ts)?;
689        Ok(())
690    }
691
692    fn drop(&mut self, id: Resource<HostOutgoingBody>) -> wasmtime::Result<()> {
693        self.table.delete(id)?.abort();
694        Ok(())
695    }
696}
697
698impl types::HostRequestOptions for WasiHttpCtxView<'_> {
699    fn new(&mut self) -> wasmtime::Result<Resource<types::RequestOptions>> {
700        let id = self.table.push(types::RequestOptions::default())?;
701        Ok(id)
702    }
703
704    fn connect_timeout(
705        &mut self,
706        opts: Resource<types::RequestOptions>,
707    ) -> wasmtime::Result<Option<types::Duration>> {
708        let nanos = self.table.get(&opts)?.connect_timeout.map(|d| d.as_nanos());
709
710        if let Some(nanos) = nanos {
711            Ok(Some(nanos.try_into()?))
712        } else {
713            Ok(None)
714        }
715    }
716
717    fn set_connect_timeout(
718        &mut self,
719        opts: Resource<types::RequestOptions>,
720        duration: Option<types::Duration>,
721    ) -> wasmtime::Result<Result<(), ()>> {
722        self.table.get_mut(&opts)?.connect_timeout = duration.map(std::time::Duration::from_nanos);
723        Ok(Ok(()))
724    }
725
726    fn first_byte_timeout(
727        &mut self,
728        opts: Resource<types::RequestOptions>,
729    ) -> wasmtime::Result<Option<types::Duration>> {
730        let nanos = self
731            .table
732            .get(&opts)?
733            .first_byte_timeout
734            .map(|d| d.as_nanos());
735
736        if let Some(nanos) = nanos {
737            Ok(Some(nanos.try_into()?))
738        } else {
739            Ok(None)
740        }
741    }
742
743    fn set_first_byte_timeout(
744        &mut self,
745        opts: Resource<types::RequestOptions>,
746        duration: Option<types::Duration>,
747    ) -> wasmtime::Result<Result<(), ()>> {
748        self.table.get_mut(&opts)?.first_byte_timeout =
749            duration.map(std::time::Duration::from_nanos);
750        Ok(Ok(()))
751    }
752
753    fn between_bytes_timeout(
754        &mut self,
755        opts: Resource<types::RequestOptions>,
756    ) -> wasmtime::Result<Option<types::Duration>> {
757        let nanos = self
758            .table
759            .get(&opts)?
760            .between_bytes_timeout
761            .map(|d| d.as_nanos());
762
763        if let Some(nanos) = nanos {
764            Ok(Some(nanos.try_into()?))
765        } else {
766            Ok(None)
767        }
768    }
769
770    fn set_between_bytes_timeout(
771        &mut self,
772        opts: Resource<types::RequestOptions>,
773        duration: Option<types::Duration>,
774    ) -> wasmtime::Result<Result<(), ()>> {
775        self.table.get_mut(&opts)?.between_bytes_timeout =
776            duration.map(std::time::Duration::from_nanos);
777        Ok(Ok(()))
778    }
779
780    fn drop(&mut self, rep: Resource<types::RequestOptions>) -> wasmtime::Result<()> {
781        let _ = self.table.delete(rep)?;
782        Ok(())
783    }
784}
785
786mod named {
787    use crate::p2::bindings::http::types::{self, Method, Scheme, StatusCode, Trailers};
788    use crate::p2::bindings::named_imports::wasi::http::types as named_types;
789    use crate::p2::body::{HostFutureTrailers, HostIncomingBody, HostOutgoingBody};
790    use crate::p2::types::{
791        HostFutureIncomingResponse, HostIncomingRequest, HostIncomingResponse, HostOutgoingRequest,
792        HostOutgoingResponse, HostResponseOutparam,
793    };
794    use crate::p2::{HeaderError, HeaderResult, HttpError, HttpResult};
795    use crate::{FieldMap, WasiHttpNamedView};
796    use wasmtime::component::Resource;
797    use wasmtime_wasi::p2::{DynInputStream, DynOutputStream, DynPollable};
798    use wasmtime_wasi::{NamedId, WasiCtxNamedView};
799
800    impl<T> named_types::Host for WasiCtxNamedView<'_, T>
801    where
802        T: WasiHttpNamedView,
803    {
804        fn convert_error_code(&mut self, err: HttpError) -> wasmtime::Result<types::ErrorCode> {
805            err.downcast()
806        }
807
808        fn convert_header_error(
809            &mut self,
810            err: HeaderError,
811        ) -> wasmtime::Result<types::HeaderError> {
812            err.downcast()
813        }
814
815        fn http_error_code(
816            &mut self,
817            id: NamedId,
818            err: wasmtime::component::Resource<types::IoError>,
819        ) -> wasmtime::Result<Option<types::ErrorCode>> {
820            super::types::Host::http_error_code(&mut self.0.http(id), err)
821        }
822    }
823
824    impl<T> named_types::HostFields for WasiCtxNamedView<'_, T>
825    where
826        T: WasiHttpNamedView,
827    {
828        fn new(&mut self, id: NamedId) -> wasmtime::Result<Resource<FieldMap>> {
829            super::types::HostFields::new(&mut self.0.http(id))
830        }
831
832        fn from_list(
833            &mut self,
834            id: NamedId,
835            entries: Vec<(String, Vec<u8>)>,
836        ) -> HeaderResult<Resource<FieldMap>> {
837            super::types::HostFields::from_list(&mut self.0.http(id), entries)
838        }
839
840        fn drop(&mut self, id: NamedId, fields: Resource<FieldMap>) -> wasmtime::Result<()> {
841            super::types::HostFields::drop(&mut self.0.http(id), fields)
842        }
843
844        fn get(
845            &mut self,
846            id: NamedId,
847            fields: Resource<FieldMap>,
848            name: String,
849        ) -> wasmtime::Result<Vec<Vec<u8>>> {
850            super::types::HostFields::get(&mut self.0.http(id), fields, name)
851        }
852
853        fn has(
854            &mut self,
855            id: NamedId,
856            fields: Resource<FieldMap>,
857            name: String,
858        ) -> wasmtime::Result<bool> {
859            super::types::HostFields::has(&mut self.0.http(id), fields, name)
860        }
861
862        fn set(
863            &mut self,
864            id: NamedId,
865            fields: Resource<FieldMap>,
866            name: String,
867            values: Vec<Vec<u8>>,
868        ) -> HeaderResult<()> {
869            super::types::HostFields::set(&mut self.0.http(id), fields, name, values)
870        }
871
872        fn delete(
873            &mut self,
874            id: NamedId,
875            fields: Resource<FieldMap>,
876            name: String,
877        ) -> HeaderResult<()> {
878            super::types::HostFields::delete(&mut self.0.http(id), fields, name)
879        }
880
881        fn append(
882            &mut self,
883            id: NamedId,
884            fields: Resource<FieldMap>,
885            name: String,
886            value: Vec<u8>,
887        ) -> HeaderResult<()> {
888            super::types::HostFields::append(&mut self.0.http(id), fields, name, value)
889        }
890
891        fn entries(
892            &mut self,
893            id: NamedId,
894            fields: Resource<FieldMap>,
895        ) -> wasmtime::Result<Vec<(String, Vec<u8>)>> {
896            super::types::HostFields::entries(&mut self.0.http(id), fields)
897        }
898
899        fn clone(
900            &mut self,
901            id: NamedId,
902            fields: Resource<FieldMap>,
903        ) -> wasmtime::Result<Resource<FieldMap>> {
904            super::types::HostFields::clone(&mut self.0.http(id), fields)
905        }
906    }
907
908    impl<T> named_types::HostIncomingRequest for WasiCtxNamedView<'_, T>
909    where
910        T: WasiHttpNamedView,
911    {
912        fn method(
913            &mut self,
914            id: NamedId,
915            this: Resource<HostIncomingRequest>,
916        ) -> wasmtime::Result<Method> {
917            super::types::HostIncomingRequest::method(&mut self.0.http(id), this)
918        }
919
920        fn path_with_query(
921            &mut self,
922            id: NamedId,
923            this: Resource<HostIncomingRequest>,
924        ) -> wasmtime::Result<Option<String>> {
925            super::types::HostIncomingRequest::path_with_query(&mut self.0.http(id), this)
926        }
927
928        fn scheme(
929            &mut self,
930            id: NamedId,
931            this: Resource<HostIncomingRequest>,
932        ) -> wasmtime::Result<Option<Scheme>> {
933            super::types::HostIncomingRequest::scheme(&mut self.0.http(id), this)
934        }
935
936        fn authority(
937            &mut self,
938            id: NamedId,
939            this: Resource<HostIncomingRequest>,
940        ) -> wasmtime::Result<Option<String>> {
941            super::types::HostIncomingRequest::authority(&mut self.0.http(id), this)
942        }
943
944        fn headers(
945            &mut self,
946            id: NamedId,
947            this: Resource<HostIncomingRequest>,
948        ) -> wasmtime::Result<Resource<FieldMap>> {
949            super::types::HostIncomingRequest::headers(&mut self.0.http(id), this)
950        }
951
952        fn consume(
953            &mut self,
954            id: NamedId,
955            this: Resource<HostIncomingRequest>,
956        ) -> wasmtime::Result<Result<Resource<HostIncomingBody>, ()>> {
957            super::types::HostIncomingRequest::consume(&mut self.0.http(id), this)
958        }
959
960        fn drop(
961            &mut self,
962            id: NamedId,
963            this: Resource<HostIncomingRequest>,
964        ) -> wasmtime::Result<()> {
965            super::types::HostIncomingRequest::drop(&mut self.0.http(id), this)
966        }
967    }
968
969    impl<T> named_types::HostOutgoingRequest for WasiCtxNamedView<'_, T>
970    where
971        T: WasiHttpNamedView,
972    {
973        fn new(
974            &mut self,
975            id: NamedId,
976            headers: Resource<FieldMap>,
977        ) -> wasmtime::Result<Resource<HostOutgoingRequest>> {
978            super::types::HostOutgoingRequest::new(&mut self.0.http(id), headers)
979        }
980
981        fn body(
982            &mut self,
983            id: NamedId,
984            request: Resource<HostOutgoingRequest>,
985        ) -> wasmtime::Result<Result<Resource<HostOutgoingBody>, ()>> {
986            super::types::HostOutgoingRequest::body(&mut self.0.http(id), request)
987        }
988
989        fn drop(
990            &mut self,
991            id: NamedId,
992            request: Resource<HostOutgoingRequest>,
993        ) -> wasmtime::Result<()> {
994            super::types::HostOutgoingRequest::drop(&mut self.0.http(id), request)
995        }
996
997        fn method(
998            &mut self,
999            id: NamedId,
1000            request: wasmtime::component::Resource<types::OutgoingRequest>,
1001        ) -> wasmtime::Result<Method> {
1002            super::types::HostOutgoingRequest::method(&mut self.0.http(id), request)
1003        }
1004
1005        fn set_method(
1006            &mut self,
1007            id: NamedId,
1008            request: wasmtime::component::Resource<types::OutgoingRequest>,
1009            method: Method,
1010        ) -> wasmtime::Result<Result<(), ()>> {
1011            super::types::HostOutgoingRequest::set_method(&mut self.0.http(id), request, method)
1012        }
1013
1014        fn path_with_query(
1015            &mut self,
1016            id: NamedId,
1017            request: wasmtime::component::Resource<types::OutgoingRequest>,
1018        ) -> wasmtime::Result<Option<String>> {
1019            super::types::HostOutgoingRequest::path_with_query(&mut self.0.http(id), request)
1020        }
1021
1022        fn set_path_with_query(
1023            &mut self,
1024            id: NamedId,
1025            request: wasmtime::component::Resource<types::OutgoingRequest>,
1026            path_with_query: Option<String>,
1027        ) -> wasmtime::Result<Result<(), ()>> {
1028            super::types::HostOutgoingRequest::set_path_with_query(
1029                &mut self.0.http(id),
1030                request,
1031                path_with_query,
1032            )
1033        }
1034
1035        fn scheme(
1036            &mut self,
1037            id: NamedId,
1038            request: wasmtime::component::Resource<types::OutgoingRequest>,
1039        ) -> wasmtime::Result<Option<Scheme>> {
1040            super::types::HostOutgoingRequest::scheme(&mut self.0.http(id), request)
1041        }
1042
1043        fn set_scheme(
1044            &mut self,
1045            id: NamedId,
1046            request: wasmtime::component::Resource<types::OutgoingRequest>,
1047            scheme: Option<Scheme>,
1048        ) -> wasmtime::Result<Result<(), ()>> {
1049            super::types::HostOutgoingRequest::set_scheme(&mut self.0.http(id), request, scheme)
1050        }
1051
1052        fn authority(
1053            &mut self,
1054            id: NamedId,
1055            request: wasmtime::component::Resource<types::OutgoingRequest>,
1056        ) -> wasmtime::Result<Option<String>> {
1057            super::types::HostOutgoingRequest::authority(&mut self.0.http(id), request)
1058        }
1059
1060        fn set_authority(
1061            &mut self,
1062            id: NamedId,
1063            request: wasmtime::component::Resource<types::OutgoingRequest>,
1064            authority: Option<String>,
1065        ) -> wasmtime::Result<Result<(), ()>> {
1066            super::types::HostOutgoingRequest::set_authority(
1067                &mut self.0.http(id),
1068                request,
1069                authority,
1070            )
1071        }
1072
1073        fn headers(
1074            &mut self,
1075            id: NamedId,
1076            request: wasmtime::component::Resource<types::OutgoingRequest>,
1077        ) -> wasmtime::Result<wasmtime::component::Resource<FieldMap>> {
1078            super::types::HostOutgoingRequest::headers(&mut self.0.http(id), request)
1079        }
1080    }
1081
1082    impl<T> named_types::HostResponseOutparam for WasiCtxNamedView<'_, T>
1083    where
1084        T: WasiHttpNamedView,
1085    {
1086        fn drop(
1087            &mut self,
1088            id: NamedId,
1089            this: Resource<HostResponseOutparam>,
1090        ) -> wasmtime::Result<()> {
1091            super::types::HostResponseOutparam::drop(&mut self.0.http(id), this)
1092        }
1093
1094        fn set(
1095            &mut self,
1096            id: NamedId,
1097            this: Resource<HostResponseOutparam>,
1098            resp: Result<Resource<HostOutgoingResponse>, types::ErrorCode>,
1099        ) -> wasmtime::Result<()> {
1100            super::types::HostResponseOutparam::set(&mut self.0.http(id), this, resp)
1101        }
1102
1103        fn send_informational(
1104            &mut self,
1105            id: NamedId,
1106            _id: Resource<HostResponseOutparam>,
1107            _status: u16,
1108            _headers: Resource<FieldMap>,
1109        ) -> HttpResult<()> {
1110            super::types::HostResponseOutparam::send_informational(
1111                &mut self.0.http(id),
1112                _id,
1113                _status,
1114                _headers,
1115            )
1116        }
1117    }
1118
1119    impl<T> named_types::HostIncomingResponse for WasiCtxNamedView<'_, T>
1120    where
1121        T: WasiHttpNamedView,
1122    {
1123        fn drop(
1124            &mut self,
1125            id: NamedId,
1126            response: Resource<HostIncomingResponse>,
1127        ) -> wasmtime::Result<()> {
1128            super::types::HostIncomingResponse::drop(&mut self.0.http(id), response)
1129        }
1130
1131        fn status(
1132            &mut self,
1133            id: NamedId,
1134            response: Resource<HostIncomingResponse>,
1135        ) -> wasmtime::Result<StatusCode> {
1136            super::types::HostIncomingResponse::status(&mut self.0.http(id), response)
1137        }
1138
1139        fn headers(
1140            &mut self,
1141            id: NamedId,
1142            response: Resource<HostIncomingResponse>,
1143        ) -> wasmtime::Result<Resource<FieldMap>> {
1144            super::types::HostIncomingResponse::headers(&mut self.0.http(id), response)
1145        }
1146
1147        fn consume(
1148            &mut self,
1149            id: NamedId,
1150            response: Resource<HostIncomingResponse>,
1151        ) -> wasmtime::Result<Result<Resource<HostIncomingBody>, ()>> {
1152            super::types::HostIncomingResponse::consume(&mut self.0.http(id), response)
1153        }
1154    }
1155
1156    impl<T> named_types::HostFutureTrailers for WasiCtxNamedView<'_, T>
1157    where
1158        T: WasiHttpNamedView,
1159    {
1160        fn drop(
1161            &mut self,
1162            id: NamedId,
1163            this: Resource<HostFutureTrailers>,
1164        ) -> wasmtime::Result<()> {
1165            super::types::HostFutureTrailers::drop(&mut self.0.http(id), this)
1166        }
1167
1168        fn subscribe(
1169            &mut self,
1170            id: NamedId,
1171            index: Resource<HostFutureTrailers>,
1172        ) -> wasmtime::Result<Resource<DynPollable>> {
1173            super::types::HostFutureTrailers::subscribe(&mut self.0.http(id), index)
1174        }
1175
1176        fn get(
1177            &mut self,
1178            id: NamedId,
1179            this: Resource<HostFutureTrailers>,
1180        ) -> wasmtime::Result<
1181            Option<Result<Result<Option<Resource<Trailers>>, types::ErrorCode>, ()>>,
1182        > {
1183            super::types::HostFutureTrailers::get(&mut self.0.http(id), this)
1184        }
1185    }
1186
1187    impl<T> named_types::HostIncomingBody for WasiCtxNamedView<'_, T>
1188    where
1189        T: WasiHttpNamedView,
1190    {
1191        fn stream(
1192            &mut self,
1193            id: NamedId,
1194            this: Resource<HostIncomingBody>,
1195        ) -> wasmtime::Result<Result<Resource<DynInputStream>, ()>> {
1196            super::types::HostIncomingBody::stream(&mut self.0.http(id), this)
1197        }
1198
1199        fn finish(
1200            &mut self,
1201            id: NamedId,
1202            this: Resource<HostIncomingBody>,
1203        ) -> wasmtime::Result<Resource<HostFutureTrailers>> {
1204            super::types::HostIncomingBody::finish(&mut self.0.http(id), this)
1205        }
1206
1207        fn drop(&mut self, id: NamedId, this: Resource<HostIncomingBody>) -> wasmtime::Result<()> {
1208            super::types::HostIncomingBody::drop(&mut self.0.http(id), this)
1209        }
1210    }
1211
1212    impl<T> named_types::HostOutgoingResponse for WasiCtxNamedView<'_, T>
1213    where
1214        T: WasiHttpNamedView,
1215    {
1216        fn new(
1217            &mut self,
1218            id: NamedId,
1219            headers: Resource<FieldMap>,
1220        ) -> wasmtime::Result<Resource<HostOutgoingResponse>> {
1221            super::types::HostOutgoingResponse::new(&mut self.0.http(id), headers)
1222        }
1223
1224        fn body(
1225            &mut self,
1226            id: NamedId,
1227            this: Resource<HostOutgoingResponse>,
1228        ) -> wasmtime::Result<Result<Resource<HostOutgoingBody>, ()>> {
1229            super::types::HostOutgoingResponse::body(&mut self.0.http(id), this)
1230        }
1231
1232        fn status_code(
1233            &mut self,
1234            id: NamedId,
1235            this: Resource<HostOutgoingResponse>,
1236        ) -> wasmtime::Result<types::StatusCode> {
1237            super::types::HostOutgoingResponse::status_code(&mut self.0.http(id), this)
1238        }
1239
1240        fn set_status_code(
1241            &mut self,
1242            id: NamedId,
1243            this: Resource<HostOutgoingResponse>,
1244            status: types::StatusCode,
1245        ) -> wasmtime::Result<Result<(), ()>> {
1246            super::types::HostOutgoingResponse::set_status_code(&mut self.0.http(id), this, status)
1247        }
1248
1249        fn headers(
1250            &mut self,
1251            id: NamedId,
1252            this: Resource<HostOutgoingResponse>,
1253        ) -> wasmtime::Result<Resource<FieldMap>> {
1254            super::types::HostOutgoingResponse::headers(&mut self.0.http(id), this)
1255        }
1256
1257        fn drop(
1258            &mut self,
1259            id: NamedId,
1260            this: Resource<HostOutgoingResponse>,
1261        ) -> wasmtime::Result<()> {
1262            super::types::HostOutgoingResponse::drop(&mut self.0.http(id), this)
1263        }
1264    }
1265
1266    impl<T> named_types::HostFutureIncomingResponse for WasiCtxNamedView<'_, T>
1267    where
1268        T: WasiHttpNamedView,
1269    {
1270        fn drop(
1271            &mut self,
1272            id: NamedId,
1273            this: Resource<HostFutureIncomingResponse>,
1274        ) -> wasmtime::Result<()> {
1275            super::types::HostFutureIncomingResponse::drop(&mut self.0.http(id), this)
1276        }
1277
1278        fn get(
1279            &mut self,
1280            id: NamedId,
1281            this: Resource<HostFutureIncomingResponse>,
1282        ) -> wasmtime::Result<
1283            Option<Result<Result<Resource<HostIncomingResponse>, types::ErrorCode>, ()>>,
1284        > {
1285            super::types::HostFutureIncomingResponse::get(&mut self.0.http(id), this)
1286        }
1287
1288        fn subscribe(
1289            &mut self,
1290            id: NamedId,
1291            this: Resource<HostFutureIncomingResponse>,
1292        ) -> wasmtime::Result<Resource<DynPollable>> {
1293            super::types::HostFutureIncomingResponse::subscribe(&mut self.0.http(id), this)
1294        }
1295    }
1296
1297    impl<T> named_types::HostOutgoingBody for WasiCtxNamedView<'_, T>
1298    where
1299        T: WasiHttpNamedView,
1300    {
1301        fn write(
1302            &mut self,
1303            id: NamedId,
1304            this: Resource<HostOutgoingBody>,
1305        ) -> wasmtime::Result<Result<Resource<DynOutputStream>, ()>> {
1306            super::types::HostOutgoingBody::write(&mut self.0.http(id), this)
1307        }
1308
1309        fn finish(
1310            &mut self,
1311            id: NamedId,
1312            this: Resource<HostOutgoingBody>,
1313            ts: Option<Resource<Trailers>>,
1314        ) -> HttpResult<()> {
1315            super::types::HostOutgoingBody::finish(&mut self.0.http(id), this, ts)
1316        }
1317
1318        fn drop(&mut self, id: NamedId, this: Resource<HostOutgoingBody>) -> wasmtime::Result<()> {
1319            super::types::HostOutgoingBody::drop(&mut self.0.http(id), this)
1320        }
1321    }
1322
1323    impl<T> named_types::HostRequestOptions for WasiCtxNamedView<'_, T>
1324    where
1325        T: WasiHttpNamedView,
1326    {
1327        fn new(&mut self, id: NamedId) -> wasmtime::Result<Resource<types::RequestOptions>> {
1328            super::types::HostRequestOptions::new(&mut self.0.http(id))
1329        }
1330
1331        fn connect_timeout(
1332            &mut self,
1333            id: NamedId,
1334            opts: Resource<types::RequestOptions>,
1335        ) -> wasmtime::Result<Option<types::Duration>> {
1336            super::types::HostRequestOptions::connect_timeout(&mut self.0.http(id), opts)
1337        }
1338
1339        fn set_connect_timeout(
1340            &mut self,
1341            id: NamedId,
1342            opts: Resource<types::RequestOptions>,
1343            duration: Option<types::Duration>,
1344        ) -> wasmtime::Result<Result<(), ()>> {
1345            super::types::HostRequestOptions::set_connect_timeout(
1346                &mut self.0.http(id),
1347                opts,
1348                duration,
1349            )
1350        }
1351
1352        fn first_byte_timeout(
1353            &mut self,
1354            id: NamedId,
1355            opts: Resource<types::RequestOptions>,
1356        ) -> wasmtime::Result<Option<types::Duration>> {
1357            super::types::HostRequestOptions::first_byte_timeout(&mut self.0.http(id), opts)
1358        }
1359
1360        fn set_first_byte_timeout(
1361            &mut self,
1362            id: NamedId,
1363            opts: Resource<types::RequestOptions>,
1364            duration: Option<types::Duration>,
1365        ) -> wasmtime::Result<Result<(), ()>> {
1366            super::types::HostRequestOptions::set_first_byte_timeout(
1367                &mut self.0.http(id),
1368                opts,
1369                duration,
1370            )
1371        }
1372
1373        fn between_bytes_timeout(
1374            &mut self,
1375            id: NamedId,
1376            opts: Resource<types::RequestOptions>,
1377        ) -> wasmtime::Result<Option<types::Duration>> {
1378            super::types::HostRequestOptions::between_bytes_timeout(&mut self.0.http(id), opts)
1379        }
1380
1381        fn set_between_bytes_timeout(
1382            &mut self,
1383            id: NamedId,
1384            opts: Resource<types::RequestOptions>,
1385            duration: Option<types::Duration>,
1386        ) -> wasmtime::Result<Result<(), ()>> {
1387            super::types::HostRequestOptions::set_between_bytes_timeout(
1388                &mut self.0.http(id),
1389                opts,
1390                duration,
1391            )
1392        }
1393
1394        fn drop(
1395            &mut self,
1396            id: NamedId,
1397            rep: Resource<types::RequestOptions>,
1398        ) -> wasmtime::Result<()> {
1399            super::types::HostRequestOptions::drop(&mut self.0.http(id), rep)
1400        }
1401    }
1402}
1403
1404#[cfg(test)]
1405mod tests {
1406    use crate::p2::bindings::http::types::HostOutgoingResponse as _;
1407    use crate::p2::types::HostOutgoingResponse;
1408    use crate::{FieldMap, WasiHttpCtx, WasiHttpCtxView};
1409
1410    fn push(view: &mut WasiHttpCtxView<'_>) -> wasmtime::component::Resource<HostOutgoingResponse> {
1411        view.table
1412            .push(HostOutgoingResponse {
1413                status: http::StatusCode::OK,
1414                headers: FieldMap::new_mutable(1024),
1415                body: None,
1416            })
1417            .expect("push outgoing-response")
1418    }
1419
1420    /// `set-status-code` must fail for codes outside the RFC 9110 range, as the
1421    /// `wasi:http` contract promises and the p3 implementation already does.
1422    #[test]
1423    fn set_status_code_range() {
1424        let mut table = wasmtime::component::ResourceTable::new();
1425        let mut http = WasiHttpCtx::new();
1426        let hooks: &mut dyn crate::WasiHttpHooks = crate::default_hooks();
1427        let mut view = WasiHttpCtxView {
1428            ctx: &mut http,
1429            table: &mut table,
1430            hooks,
1431        };
1432
1433        for code in [100u16, 199, 200, 599] {
1434            let id = push(&mut view);
1435            let r = view.set_status_code(id, code);
1436            assert!(
1437                matches!(r, Ok(Ok(()))),
1438                "code {code} should be accepted: {r:?}"
1439            );
1440        }
1441
1442        for code in [0u16, 99, 600, 999, 1000, 65535] {
1443            let id = push(&mut view);
1444            let r = view.set_status_code(id, code);
1445            assert!(
1446                matches!(r, Ok(Err(()))),
1447                "code {code} should be rejected: {r:?}"
1448            );
1449        }
1450    }
1451}