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        Ok(e.downcast_ref::<types::ErrorCode>().cloned())
32    }
33}
34
35impl types::HostFields for WasiHttpCtxView<'_> {
36    fn new(&mut self) -> wasmtime::Result<Resource<FieldMap>> {
37        let limit = self.ctx.field_size_limit;
38        let id = self
39            .table
40            .push(FieldMap::new_mutable(limit))
41            .context("[new_fields] pushing fields")?;
42
43        Ok(id)
44    }
45
46    fn from_list(&mut self, entries: Vec<(String, Vec<u8>)>) -> HeaderResult<Resource<FieldMap>> {
47        let mut fields = FieldMap::new_mutable(self.ctx.field_size_limit);
48
49        for (header, value) in entries {
50            fields.append(self.hooks, header, value)?;
51        }
52
53        Ok(self.table.push(fields)?)
54    }
55
56    fn drop(&mut self, fields: Resource<FieldMap>) -> wasmtime::Result<()> {
57        self.table
58            .delete(fields)
59            .context("[drop_fields] deleting fields")?;
60        Ok(())
61    }
62
63    fn get(&mut self, fields: Resource<FieldMap>, name: String) -> wasmtime::Result<Vec<Vec<u8>>> {
64        let fields = self.table.get(&fields)?;
65
66        let header = match HeaderName::from_bytes(name.as_bytes()) {
67            Ok(header) => header,
68            Err(_) => return Ok(vec![]),
69        };
70
71        if !fields.contains_key(&header) {
72            return Ok(vec![]);
73        }
74
75        let res = fields
76            .get_all(&header)
77            .into_iter()
78            .map(|val| val.as_bytes().to_owned())
79            .collect();
80        Ok(res)
81    }
82
83    fn has(&mut self, fields: Resource<FieldMap>, name: String) -> wasmtime::Result<bool> {
84        let fields = self.table.get(&fields)?;
85
86        match HeaderName::from_bytes(name.as_bytes()) {
87            Ok(header) => Ok(fields.contains_key(&header)),
88            Err(_) => Ok(false),
89        }
90    }
91
92    fn set(
93        &mut self,
94        fields: Resource<FieldMap>,
95        name: String,
96        values: Vec<Vec<u8>>,
97    ) -> HeaderResult<()> {
98        let fields = self.table.get_mut(&fields)?;
99        fields.set(self.hooks, name, values)?;
100        Ok(())
101    }
102
103    fn delete(&mut self, fields: Resource<FieldMap>, name: String) -> HeaderResult<()> {
104        let fields = self.table.get_mut(&fields)?;
105        fields.remove_all(self.hooks, name)?;
106        Ok(())
107    }
108
109    fn append(
110        &mut self,
111        fields: Resource<FieldMap>,
112        name: String,
113        value: Vec<u8>,
114    ) -> HeaderResult<()> {
115        let fields = self.table.get_mut(&fields)?;
116        fields.append(self.hooks, name, value)?;
117        Ok(())
118    }
119
120    fn entries(&mut self, fields: Resource<FieldMap>) -> wasmtime::Result<Vec<(String, Vec<u8>)>> {
121        Ok(self
122            .table
123            .get(&fields)?
124            .iter()
125            .map(|(name, value)| (name.as_str().to_owned(), value.as_bytes().to_owned()))
126            .collect())
127    }
128
129    fn clone(&mut self, fields: Resource<FieldMap>) -> wasmtime::Result<Resource<FieldMap>> {
130        let mut fields = self.table.get(&fields)?.clone();
131        fields.set_mutable(self.ctx.field_size_limit);
132        let id = self.table.push(fields)?;
133        Ok(id)
134    }
135}
136
137impl types::HostIncomingRequest for WasiHttpCtxView<'_> {
138    fn method(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<Method> {
139        let method = self.table.get(&id)?.method.clone();
140        Ok(method.into())
141    }
142    fn path_with_query(
143        &mut self,
144        id: Resource<HostIncomingRequest>,
145    ) -> wasmtime::Result<Option<String>> {
146        let req = self.table.get(&id)?;
147        Ok(req
148            .uri
149            .path_and_query()
150            .map(|path_and_query| path_and_query.as_str().to_owned()))
151    }
152    fn scheme(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<Option<Scheme>> {
153        let req = self.table.get(&id)?;
154        Ok(Some(req.scheme.clone()))
155    }
156    fn authority(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<Option<String>> {
157        let req = self.table.get(&id)?;
158        Ok(Some(req.authority.clone()))
159    }
160
161    fn headers(
162        &mut self,
163        id: Resource<HostIncomingRequest>,
164    ) -> wasmtime::Result<Resource<FieldMap>> {
165        let req = self.table.get(&id)?;
166        Ok(self.table.push(req.headers.clone())?)
167    }
168
169    fn consume(
170        &mut self,
171        id: Resource<HostIncomingRequest>,
172    ) -> wasmtime::Result<Result<Resource<HostIncomingBody>, ()>> {
173        let req = self.table.get_mut(&id)?;
174        match req.body.take() {
175            Some(body) => {
176                let id = self.table.push(body)?;
177                Ok(Ok(id))
178            }
179
180            None => Ok(Err(())),
181        }
182    }
183
184    fn drop(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<()> {
185        let _ = self.table.delete(id)?;
186        Ok(())
187    }
188}
189
190impl types::HostOutgoingRequest for WasiHttpCtxView<'_> {
191    fn new(
192        &mut self,
193        headers: Resource<FieldMap>,
194    ) -> wasmtime::Result<Resource<HostOutgoingRequest>> {
195        let mut headers = self.table.delete(headers)?;
196        headers.set_immutable();
197
198        self.table
199            .push(HostOutgoingRequest {
200                path_with_query: None,
201                authority: None,
202                method: types::Method::Get,
203                headers,
204                scheme: None,
205                body: None,
206            })
207            .context("[new_outgoing_request] pushing request")
208    }
209
210    fn body(
211        &mut self,
212        request: Resource<HostOutgoingRequest>,
213    ) -> wasmtime::Result<Result<Resource<HostOutgoingBody>, ()>> {
214        let buffer_chunks = self.hooks.p2_outgoing_body_buffer_chunks();
215        let chunk_size = self.hooks.p2_outgoing_body_chunk_size();
216        let req = self
217            .table
218            .get_mut(&request)
219            .context("[outgoing_request_write] getting request")?;
220
221        if req.body.is_some() {
222            return Ok(Err(()));
223        }
224
225        let size = match get_content_length(&req.headers) {
226            Ok(size) => size,
227            Err(..) => return Ok(Err(())),
228        };
229
230        let (host_body, hyper_body) =
231            HostOutgoingBody::new(StreamContext::Request, size, buffer_chunks, chunk_size);
232
233        req.body = Some(hyper_body);
234
235        // The output stream will necessarily outlive the request, because we could be still
236        // writing to the stream after `outgoing-handler.handle` is called.
237        let outgoing_body = self.table.push(host_body)?;
238
239        Ok(Ok(outgoing_body))
240    }
241
242    fn drop(&mut self, request: Resource<HostOutgoingRequest>) -> wasmtime::Result<()> {
243        let _ = self.table.delete(request)?;
244        Ok(())
245    }
246
247    fn method(
248        &mut self,
249        request: wasmtime::component::Resource<types::OutgoingRequest>,
250    ) -> wasmtime::Result<Method> {
251        Ok(self.table.get(&request)?.method.clone())
252    }
253
254    fn set_method(
255        &mut self,
256        request: wasmtime::component::Resource<types::OutgoingRequest>,
257        method: Method,
258    ) -> wasmtime::Result<Result<(), ()>> {
259        let req = self.table.get_mut(&request)?;
260
261        if let Method::Other(s) = &method {
262            if let Err(_) = http::Method::from_str(s) {
263                return Ok(Err(()));
264            }
265        }
266
267        req.method = method;
268
269        Ok(Ok(()))
270    }
271
272    fn path_with_query(
273        &mut self,
274        request: wasmtime::component::Resource<types::OutgoingRequest>,
275    ) -> wasmtime::Result<Option<String>> {
276        Ok(self.table.get(&request)?.path_with_query.clone())
277    }
278
279    fn set_path_with_query(
280        &mut self,
281        request: wasmtime::component::Resource<types::OutgoingRequest>,
282        path_with_query: Option<String>,
283    ) -> wasmtime::Result<Result<(), ()>> {
284        let req = self.table.get_mut(&request)?;
285
286        if let Some(s) = path_with_query.as_ref() {
287            if let Err(_) = http::uri::PathAndQuery::from_str(s) {
288                return Ok(Err(()));
289            }
290        }
291
292        req.path_with_query = path_with_query;
293
294        Ok(Ok(()))
295    }
296
297    fn scheme(
298        &mut self,
299        request: wasmtime::component::Resource<types::OutgoingRequest>,
300    ) -> wasmtime::Result<Option<Scheme>> {
301        Ok(self.table.get(&request)?.scheme.clone())
302    }
303
304    fn set_scheme(
305        &mut self,
306        request: wasmtime::component::Resource<types::OutgoingRequest>,
307        scheme: Option<Scheme>,
308    ) -> wasmtime::Result<Result<(), ()>> {
309        let req = self.table.get_mut(&request)?;
310
311        if let Some(types::Scheme::Other(s)) = scheme.as_ref() {
312            if let Err(_) = http::uri::Scheme::from_str(s.as_str()) {
313                return Ok(Err(()));
314            }
315        }
316
317        req.scheme = scheme;
318
319        Ok(Ok(()))
320    }
321
322    fn authority(
323        &mut self,
324        request: wasmtime::component::Resource<types::OutgoingRequest>,
325    ) -> wasmtime::Result<Option<String>> {
326        Ok(self.table.get(&request)?.authority.clone())
327    }
328
329    fn set_authority(
330        &mut self,
331        request: wasmtime::component::Resource<types::OutgoingRequest>,
332        authority: Option<String>,
333    ) -> wasmtime::Result<Result<(), ()>> {
334        let req = self.table.get_mut(&request)?;
335
336        // Match p3: reject empty / non-numeric / out-of-range ports that
337        // `http::uri::Authority` alone would accept (see crate::parse_authority).
338        if let Some(s) = authority {
339            let Ok(parsed) = crate::parse_authority(s) else {
340                return Ok(Err(()));
341            };
342            req.authority = Some(parsed.as_str().into());
343        } else {
344            req.authority = None;
345        }
346
347        Ok(Ok(()))
348    }
349
350    fn headers(
351        &mut self,
352        request: wasmtime::component::Resource<types::OutgoingRequest>,
353    ) -> wasmtime::Result<wasmtime::component::Resource<FieldMap>> {
354        let req = self.table.get(&request)?;
355        let id = self.table.push(req.headers.clone())?;
356        Ok(id)
357    }
358}
359
360impl types::HostResponseOutparam for WasiHttpCtxView<'_> {
361    fn drop(&mut self, id: Resource<HostResponseOutparam>) -> wasmtime::Result<()> {
362        let _ = self.table.delete(id)?;
363        Ok(())
364    }
365    fn set(
366        &mut self,
367        id: Resource<HostResponseOutparam>,
368        resp: Result<Resource<HostOutgoingResponse>, types::ErrorCode>,
369    ) -> wasmtime::Result<()> {
370        let val = match resp {
371            Ok(resp) => Ok(self.table.delete(resp)?.try_into()?),
372            Err(e) => Err(e),
373        };
374
375        let resp = self.table.delete(id)?;
376        (resp.send)(val);
377        Ok(())
378    }
379
380    fn send_informational(
381        &mut self,
382        _id: Resource<HostResponseOutparam>,
383        _status: u16,
384        _headers: Resource<FieldMap>,
385    ) -> HttpResult<()> {
386        Err(HttpError::trap(format_err!("not implemented")))
387    }
388}
389
390impl types::HostIncomingResponse for WasiHttpCtxView<'_> {
391    fn drop(&mut self, response: Resource<HostIncomingResponse>) -> wasmtime::Result<()> {
392        let _ = self
393            .table
394            .delete(response)
395            .context("[drop_incoming_response] deleting response")?;
396        Ok(())
397    }
398
399    fn status(&mut self, response: Resource<HostIncomingResponse>) -> wasmtime::Result<StatusCode> {
400        let r = self
401            .table
402            .get(&response)
403            .context("[incoming_response_status] getting response")?;
404        Ok(r.status)
405    }
406
407    fn headers(
408        &mut self,
409        response: Resource<HostIncomingResponse>,
410    ) -> wasmtime::Result<Resource<FieldMap>> {
411        let resp = self.table.get(&response)?;
412        let id = self.table.push(resp.headers.clone())?;
413        Ok(id)
414    }
415
416    fn consume(
417        &mut self,
418        response: Resource<HostIncomingResponse>,
419    ) -> wasmtime::Result<Result<Resource<HostIncomingBody>, ()>> {
420        let r = self
421            .table
422            .get_mut(&response)
423            .context("[incoming_response_consume] getting response")?;
424
425        match r.body.take() {
426            Some(body) => {
427                let id = self.table.push(body)?;
428                Ok(Ok(id))
429            }
430
431            None => Ok(Err(())),
432        }
433    }
434}
435
436impl types::HostFutureTrailers for WasiHttpCtxView<'_> {
437    fn drop(&mut self, id: Resource<HostFutureTrailers>) -> wasmtime::Result<()> {
438        let _ = self
439            .table
440            .delete(id)
441            .context("[drop future-trailers] deleting future-trailers")?;
442        Ok(())
443    }
444
445    fn subscribe(
446        &mut self,
447        index: Resource<HostFutureTrailers>,
448    ) -> wasmtime::Result<Resource<DynPollable>> {
449        wasmtime_wasi::p2::subscribe(self.table, index)
450    }
451
452    fn get(
453        &mut self,
454        id: Resource<HostFutureTrailers>,
455    ) -> wasmtime::Result<Option<Result<Result<Option<Resource<Trailers>>, types::ErrorCode>, ()>>>
456    {
457        let trailers = self.table.get_mut(&id)?;
458        match trailers {
459            HostFutureTrailers::Waiting { .. } => return Ok(None),
460            HostFutureTrailers::Consumed => return Ok(Some(Err(()))),
461            HostFutureTrailers::Done(_) => {}
462        };
463
464        let res = match std::mem::replace(trailers, HostFutureTrailers::Consumed) {
465            HostFutureTrailers::Done(res) => res,
466            _ => unreachable!(),
467        };
468
469        let fields = match res {
470            Ok(Some(fields)) => fields,
471            Ok(None) => return Ok(Some(Ok(Ok(None)))),
472            Err(e) => {
473                let e = self.error_to_p2(e);
474                return Ok(Some(Ok(Err(e))));
475            }
476        };
477
478        let ts = self
479            .table
480            .push(FieldMap::new_immutable(self.hooks, fields))?;
481
482        Ok(Some(Ok(Ok(Some(ts)))))
483    }
484}
485
486impl types::HostIncomingBody for WasiHttpCtxView<'_> {
487    fn stream(
488        &mut self,
489        id: Resource<HostIncomingBody>,
490    ) -> wasmtime::Result<Result<Resource<DynInputStream>, ()>> {
491        let body = self.table.get_mut(&id)?;
492
493        if let Some(stream) = body.take_stream() {
494            let stream: DynInputStream = Box::new(stream);
495            let stream = self.table.push_child(stream, &id)?;
496            return Ok(Ok(stream));
497        }
498
499        Ok(Err(()))
500    }
501
502    fn finish(
503        &mut self,
504        id: Resource<HostIncomingBody>,
505    ) -> wasmtime::Result<Resource<HostFutureTrailers>> {
506        let body = self.table.delete(id)?;
507        let trailers = self.table.push(body.into_future_trailers())?;
508        Ok(trailers)
509    }
510
511    fn drop(&mut self, id: Resource<HostIncomingBody>) -> wasmtime::Result<()> {
512        let _ = self.table.delete(id)?;
513        Ok(())
514    }
515}
516
517impl types::HostOutgoingResponse for WasiHttpCtxView<'_> {
518    fn new(
519        &mut self,
520        headers: Resource<FieldMap>,
521    ) -> wasmtime::Result<Resource<HostOutgoingResponse>> {
522        let mut fields = self.table.delete(headers)?;
523        fields.set_immutable();
524
525        let id = self.table.push(HostOutgoingResponse {
526            status: http::StatusCode::OK,
527            headers: fields,
528            body: None,
529        })?;
530
531        Ok(id)
532    }
533
534    fn body(
535        &mut self,
536        id: Resource<HostOutgoingResponse>,
537    ) -> wasmtime::Result<Result<Resource<HostOutgoingBody>, ()>> {
538        let buffer_chunks = self.hooks.p2_outgoing_body_buffer_chunks();
539        let chunk_size = self.hooks.p2_outgoing_body_chunk_size();
540        let resp = self.table.get_mut(&id)?;
541
542        if resp.body.is_some() {
543            return Ok(Err(()));
544        }
545
546        let size = match get_content_length(&resp.headers) {
547            Ok(size) => size,
548            Err(..) => return Ok(Err(())),
549        };
550
551        let (host, body) =
552            HostOutgoingBody::new(StreamContext::Response, size, buffer_chunks, chunk_size);
553
554        resp.body.replace(body);
555
556        let id = self.table.push(host)?;
557
558        Ok(Ok(id))
559    }
560
561    fn status_code(
562        &mut self,
563        id: Resource<HostOutgoingResponse>,
564    ) -> wasmtime::Result<types::StatusCode> {
565        Ok(self.table.get(&id)?.status.into())
566    }
567
568    fn set_status_code(
569        &mut self,
570        id: Resource<HostOutgoingResponse>,
571        status: types::StatusCode,
572    ) -> wasmtime::Result<Result<(), ()>> {
573        let resp = self.table.get_mut(&id)?;
574
575        match http::StatusCode::from_u16(status) {
576            Ok(status) => resp.status = status,
577            Err(_) => return Ok(Err(())),
578        };
579
580        Ok(Ok(()))
581    }
582
583    fn headers(
584        &mut self,
585        id: Resource<HostOutgoingResponse>,
586    ) -> wasmtime::Result<Resource<FieldMap>> {
587        let resp = self.table.get(&id)?;
588        Ok(self.table.push(resp.headers.clone())?)
589    }
590
591    fn drop(&mut self, id: Resource<HostOutgoingResponse>) -> wasmtime::Result<()> {
592        let _ = self.table.delete(id)?;
593        Ok(())
594    }
595}
596
597impl types::HostFutureIncomingResponse for WasiHttpCtxView<'_> {
598    fn drop(&mut self, id: Resource<HostFutureIncomingResponse>) -> wasmtime::Result<()> {
599        let _ = self.table.delete(id)?;
600        Ok(())
601    }
602
603    fn get(
604        &mut self,
605        id: Resource<HostFutureIncomingResponse>,
606    ) -> wasmtime::Result<
607        Option<Result<Result<Resource<HostIncomingResponse>, types::ErrorCode>, ()>>,
608    > {
609        let resp = self.table.get_mut(&id)?;
610
611        match resp {
612            HostFutureIncomingResponse::Pending(_) => return Ok(None),
613            HostFutureIncomingResponse::Consumed => return Ok(Some(Err(()))),
614            HostFutureIncomingResponse::Ready(_) => {}
615        }
616
617        let (resp, io) =
618            match std::mem::replace(resp, HostFutureIncomingResponse::Consumed).unwrap_ready() {
619                Ok(pair) => pair,
620                Err(e) => {
621                    let e = self.error_to_p2(e);
622                    return Ok(Some(Ok(Err(e))));
623                }
624            };
625
626        let (parts, body) = resp.into_parts();
627        let headers = FieldMap::new_immutable(self.hooks, parts.headers);
628
629        let resp = self.table.push(HostIncomingResponse {
630            status: parts.status.as_u16(),
631            headers,
632            body: Some({
633                let mut body = HostIncomingBody::new(body);
634                body.retain_worker(io);
635                body
636            }),
637        })?;
638
639        Ok(Some(Ok(Ok(resp))))
640    }
641
642    fn subscribe(
643        &mut self,
644        id: Resource<HostFutureIncomingResponse>,
645    ) -> wasmtime::Result<Resource<DynPollable>> {
646        wasmtime_wasi::p2::subscribe(self.table, id)
647    }
648}
649
650impl types::HostOutgoingBody for WasiHttpCtxView<'_> {
651    fn write(
652        &mut self,
653        id: Resource<HostOutgoingBody>,
654    ) -> wasmtime::Result<Result<Resource<DynOutputStream>, ()>> {
655        let body = self.table.get_mut(&id)?;
656        if let Some(stream) = body.take_output_stream() {
657            let id = self.table.push_child(stream, &id)?;
658            Ok(Ok(id))
659        } else {
660            Ok(Err(()))
661        }
662    }
663
664    fn finish(
665        &mut self,
666        id: Resource<HostOutgoingBody>,
667        ts: Option<Resource<Trailers>>,
668    ) -> HttpResult<()> {
669        let body = self.table.delete(id)?;
670
671        let ts = if let Some(ts) = ts {
672            Some(self.table.delete(ts)?)
673        } else {
674            None
675        };
676
677        body.finish(ts)?;
678        Ok(())
679    }
680
681    fn drop(&mut self, id: Resource<HostOutgoingBody>) -> wasmtime::Result<()> {
682        self.table.delete(id)?.abort();
683        Ok(())
684    }
685}
686
687impl types::HostRequestOptions for WasiHttpCtxView<'_> {
688    fn new(&mut self) -> wasmtime::Result<Resource<types::RequestOptions>> {
689        let id = self.table.push(types::RequestOptions::default())?;
690        Ok(id)
691    }
692
693    fn connect_timeout(
694        &mut self,
695        opts: Resource<types::RequestOptions>,
696    ) -> wasmtime::Result<Option<types::Duration>> {
697        let nanos = self.table.get(&opts)?.connect_timeout.map(|d| d.as_nanos());
698
699        if let Some(nanos) = nanos {
700            Ok(Some(nanos.try_into()?))
701        } else {
702            Ok(None)
703        }
704    }
705
706    fn set_connect_timeout(
707        &mut self,
708        opts: Resource<types::RequestOptions>,
709        duration: Option<types::Duration>,
710    ) -> wasmtime::Result<Result<(), ()>> {
711        self.table.get_mut(&opts)?.connect_timeout = duration.map(std::time::Duration::from_nanos);
712        Ok(Ok(()))
713    }
714
715    fn first_byte_timeout(
716        &mut self,
717        opts: Resource<types::RequestOptions>,
718    ) -> wasmtime::Result<Option<types::Duration>> {
719        let nanos = self
720            .table
721            .get(&opts)?
722            .first_byte_timeout
723            .map(|d| d.as_nanos());
724
725        if let Some(nanos) = nanos {
726            Ok(Some(nanos.try_into()?))
727        } else {
728            Ok(None)
729        }
730    }
731
732    fn set_first_byte_timeout(
733        &mut self,
734        opts: Resource<types::RequestOptions>,
735        duration: Option<types::Duration>,
736    ) -> wasmtime::Result<Result<(), ()>> {
737        self.table.get_mut(&opts)?.first_byte_timeout =
738            duration.map(std::time::Duration::from_nanos);
739        Ok(Ok(()))
740    }
741
742    fn between_bytes_timeout(
743        &mut self,
744        opts: Resource<types::RequestOptions>,
745    ) -> wasmtime::Result<Option<types::Duration>> {
746        let nanos = self
747            .table
748            .get(&opts)?
749            .between_bytes_timeout
750            .map(|d| d.as_nanos());
751
752        if let Some(nanos) = nanos {
753            Ok(Some(nanos.try_into()?))
754        } else {
755            Ok(None)
756        }
757    }
758
759    fn set_between_bytes_timeout(
760        &mut self,
761        opts: Resource<types::RequestOptions>,
762        duration: Option<types::Duration>,
763    ) -> wasmtime::Result<Result<(), ()>> {
764        self.table.get_mut(&opts)?.between_bytes_timeout =
765            duration.map(std::time::Duration::from_nanos);
766        Ok(Ok(()))
767    }
768
769    fn drop(&mut self, rep: Resource<types::RequestOptions>) -> wasmtime::Result<()> {
770        let _ = self.table.delete(rep)?;
771        Ok(())
772    }
773}