Skip to main content

wasmtime_wasi_http/p2/
types_impl.rs

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