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        (resp.send)(val);
405        Ok(())
406    }
407
408    fn send_informational(
409        &mut self,
410        _id: Resource<HostResponseOutparam>,
411        _status: u16,
412        _headers: Resource<FieldMap>,
413    ) -> HttpResult<()> {
414        Err(HttpError::trap(format_err!("not implemented")))
415    }
416}
417
418impl types::HostIncomingResponse for WasiHttpCtxView<'_> {
419    fn drop(&mut self, response: Resource<HostIncomingResponse>) -> wasmtime::Result<()> {
420        let _ = self
421            .table
422            .delete(response)
423            .context("[drop_incoming_response] deleting response")?;
424        Ok(())
425    }
426
427    fn status(&mut self, response: Resource<HostIncomingResponse>) -> wasmtime::Result<StatusCode> {
428        let r = self
429            .table
430            .get(&response)
431            .context("[incoming_response_status] getting response")?;
432        Ok(r.status)
433    }
434
435    fn headers(
436        &mut self,
437        response: Resource<HostIncomingResponse>,
438    ) -> wasmtime::Result<Resource<FieldMap>> {
439        let resp = self.table.get(&response)?;
440        let id = self.table.push(resp.headers.clone())?;
441        Ok(id)
442    }
443
444    fn consume(
445        &mut self,
446        response: Resource<HostIncomingResponse>,
447    ) -> wasmtime::Result<Result<Resource<HostIncomingBody>, ()>> {
448        let r = self
449            .table
450            .get_mut(&response)
451            .context("[incoming_response_consume] getting response")?;
452
453        match r.body.take() {
454            Some(body) => {
455                let id = self.table.push(body)?;
456                Ok(Ok(id))
457            }
458
459            None => Ok(Err(())),
460        }
461    }
462}
463
464impl types::HostFutureTrailers for WasiHttpCtxView<'_> {
465    fn drop(&mut self, id: Resource<HostFutureTrailers>) -> wasmtime::Result<()> {
466        let _ = self
467            .table
468            .delete(id)
469            .context("[drop future-trailers] deleting future-trailers")?;
470        Ok(())
471    }
472
473    fn subscribe(
474        &mut self,
475        index: Resource<HostFutureTrailers>,
476    ) -> wasmtime::Result<Resource<DynPollable>> {
477        wasmtime_wasi::p2::subscribe(self.table, index)
478    }
479
480    fn get(
481        &mut self,
482        id: Resource<HostFutureTrailers>,
483    ) -> wasmtime::Result<Option<Result<Result<Option<Resource<Trailers>>, types::ErrorCode>, ()>>>
484    {
485        let trailers = self.table.get_mut(&id)?;
486        match trailers {
487            HostFutureTrailers::Waiting { .. } => return Ok(None),
488            HostFutureTrailers::Consumed => return Ok(Some(Err(()))),
489            HostFutureTrailers::Done(_) => {}
490        };
491
492        let res = match std::mem::replace(trailers, HostFutureTrailers::Consumed) {
493            HostFutureTrailers::Done(res) => res,
494            _ => unreachable!(),
495        };
496
497        let mut fields = match res {
498            Ok(Some(fields)) => fields,
499            Ok(None) => return Ok(Some(Ok(Ok(None)))),
500            Err(e) => return Ok(Some(Ok(Err(e)))),
501        };
502
503        remove_forbidden_headers(self.hooks, &mut fields);
504
505        let ts = self.table.push(FieldMap::new_immutable(fields))?;
506
507        Ok(Some(Ok(Ok(Some(ts)))))
508    }
509}
510
511impl types::HostIncomingBody for WasiHttpCtxView<'_> {
512    fn stream(
513        &mut self,
514        id: Resource<HostIncomingBody>,
515    ) -> wasmtime::Result<Result<Resource<DynInputStream>, ()>> {
516        let body = self.table.get_mut(&id)?;
517
518        if let Some(stream) = body.take_stream() {
519            let stream: DynInputStream = Box::new(stream);
520            let stream = self.table.push_child(stream, &id)?;
521            return Ok(Ok(stream));
522        }
523
524        Ok(Err(()))
525    }
526
527    fn finish(
528        &mut self,
529        id: Resource<HostIncomingBody>,
530    ) -> wasmtime::Result<Resource<HostFutureTrailers>> {
531        let body = self.table.delete(id)?;
532        let trailers = self.table.push(body.into_future_trailers())?;
533        Ok(trailers)
534    }
535
536    fn drop(&mut self, id: Resource<HostIncomingBody>) -> wasmtime::Result<()> {
537        let _ = self.table.delete(id)?;
538        Ok(())
539    }
540}
541
542impl types::HostOutgoingResponse for WasiHttpCtxView<'_> {
543    fn new(
544        &mut self,
545        headers: Resource<FieldMap>,
546    ) -> wasmtime::Result<Resource<HostOutgoingResponse>> {
547        let mut fields = self.table.delete(headers)?;
548        fields.set_immutable();
549
550        let id = self.table.push(HostOutgoingResponse {
551            status: http::StatusCode::OK,
552            headers: fields,
553            body: None,
554        })?;
555
556        Ok(id)
557    }
558
559    fn body(
560        &mut self,
561        id: Resource<HostOutgoingResponse>,
562    ) -> wasmtime::Result<Result<Resource<HostOutgoingBody>, ()>> {
563        let buffer_chunks = self.hooks.outgoing_body_buffer_chunks();
564        let chunk_size = self.hooks.outgoing_body_chunk_size();
565        let resp = self.table.get_mut(&id)?;
566
567        if resp.body.is_some() {
568            return Ok(Err(()));
569        }
570
571        let size = match get_content_length(&resp.headers) {
572            Ok(size) => size,
573            Err(..) => return Ok(Err(())),
574        };
575
576        let (host, body) =
577            HostOutgoingBody::new(StreamContext::Response, size, buffer_chunks, chunk_size);
578
579        resp.body.replace(body);
580
581        let id = self.table.push(host)?;
582
583        Ok(Ok(id))
584    }
585
586    fn status_code(
587        &mut self,
588        id: Resource<HostOutgoingResponse>,
589    ) -> wasmtime::Result<types::StatusCode> {
590        Ok(self.table.get(&id)?.status.into())
591    }
592
593    fn set_status_code(
594        &mut self,
595        id: Resource<HostOutgoingResponse>,
596        status: types::StatusCode,
597    ) -> wasmtime::Result<Result<(), ()>> {
598        let resp = self.table.get_mut(&id)?;
599
600        match http::StatusCode::from_u16(status) {
601            Ok(status) => resp.status = status,
602            Err(_) => return Ok(Err(())),
603        };
604
605        Ok(Ok(()))
606    }
607
608    fn headers(
609        &mut self,
610        id: Resource<HostOutgoingResponse>,
611    ) -> wasmtime::Result<Resource<FieldMap>> {
612        let resp = self.table.get(&id)?;
613        Ok(self.table.push(resp.headers.clone())?)
614    }
615
616    fn drop(&mut self, id: Resource<HostOutgoingResponse>) -> wasmtime::Result<()> {
617        let _ = self.table.delete(id)?;
618        Ok(())
619    }
620}
621
622impl types::HostFutureIncomingResponse for WasiHttpCtxView<'_> {
623    fn drop(&mut self, id: Resource<HostFutureIncomingResponse>) -> wasmtime::Result<()> {
624        let _ = self.table.delete(id)?;
625        Ok(())
626    }
627
628    fn get(
629        &mut self,
630        id: Resource<HostFutureIncomingResponse>,
631    ) -> wasmtime::Result<
632        Option<Result<Result<Resource<HostIncomingResponse>, types::ErrorCode>, ()>>,
633    > {
634        let resp = self.table.get_mut(&id)?;
635
636        match resp {
637            HostFutureIncomingResponse::Pending(_) => return Ok(None),
638            HostFutureIncomingResponse::Consumed => return Ok(Some(Err(()))),
639            HostFutureIncomingResponse::Ready(_) => {}
640        }
641
642        let resp =
643            match std::mem::replace(resp, HostFutureIncomingResponse::Consumed).unwrap_ready() {
644                Err(e) => {
645                    // Trapping if it's not possible to downcast to an wasi-http error
646                    let e = e.downcast::<types::ErrorCode>()?;
647                    return Ok(Some(Ok(Err(e))));
648                }
649
650                Ok(Ok(resp)) => resp,
651                Ok(Err(e)) => return Ok(Some(Ok(Err(e)))),
652            };
653
654        let (mut parts, body) = resp.resp.into_parts();
655        remove_forbidden_headers(self.hooks, &mut parts.headers);
656        let headers = FieldMap::new_immutable(parts.headers);
657
658        let resp = self.table.push(HostIncomingResponse {
659            status: parts.status.as_u16(),
660            headers,
661            body: Some({
662                let mut body = HostIncomingBody::new(body, resp.between_bytes_timeout);
663                if let Some(worker) = resp.worker {
664                    body.retain_worker(worker);
665                }
666                body
667            }),
668        })?;
669
670        Ok(Some(Ok(Ok(resp))))
671    }
672
673    fn subscribe(
674        &mut self,
675        id: Resource<HostFutureIncomingResponse>,
676    ) -> wasmtime::Result<Resource<DynPollable>> {
677        wasmtime_wasi::p2::subscribe(self.table, id)
678    }
679}
680
681impl types::HostOutgoingBody for WasiHttpCtxView<'_> {
682    fn write(
683        &mut self,
684        id: Resource<HostOutgoingBody>,
685    ) -> wasmtime::Result<Result<Resource<DynOutputStream>, ()>> {
686        let body = self.table.get_mut(&id)?;
687        if let Some(stream) = body.take_output_stream() {
688            let id = self.table.push_child(stream, &id)?;
689            Ok(Ok(id))
690        } else {
691            Ok(Err(()))
692        }
693    }
694
695    fn finish(
696        &mut self,
697        id: Resource<HostOutgoingBody>,
698        ts: Option<Resource<Trailers>>,
699    ) -> HttpResult<()> {
700        let body = self.table.delete(id)?;
701
702        let ts = if let Some(ts) = ts {
703            Some(self.table.delete(ts)?)
704        } else {
705            None
706        };
707
708        body.finish(ts)?;
709        Ok(())
710    }
711
712    fn drop(&mut self, id: Resource<HostOutgoingBody>) -> wasmtime::Result<()> {
713        self.table.delete(id)?.abort();
714        Ok(())
715    }
716}
717
718impl types::HostRequestOptions for WasiHttpCtxView<'_> {
719    fn new(&mut self) -> wasmtime::Result<Resource<types::RequestOptions>> {
720        let id = self.table.push(types::RequestOptions::default())?;
721        Ok(id)
722    }
723
724    fn connect_timeout(
725        &mut self,
726        opts: Resource<types::RequestOptions>,
727    ) -> wasmtime::Result<Option<types::Duration>> {
728        let nanos = self.table.get(&opts)?.connect_timeout.map(|d| d.as_nanos());
729
730        if let Some(nanos) = nanos {
731            Ok(Some(nanos.try_into()?))
732        } else {
733            Ok(None)
734        }
735    }
736
737    fn set_connect_timeout(
738        &mut self,
739        opts: Resource<types::RequestOptions>,
740        duration: Option<types::Duration>,
741    ) -> wasmtime::Result<Result<(), ()>> {
742        self.table.get_mut(&opts)?.connect_timeout = duration.map(std::time::Duration::from_nanos);
743        Ok(Ok(()))
744    }
745
746    fn first_byte_timeout(
747        &mut self,
748        opts: Resource<types::RequestOptions>,
749    ) -> wasmtime::Result<Option<types::Duration>> {
750        let nanos = self
751            .table
752            .get(&opts)?
753            .first_byte_timeout
754            .map(|d| d.as_nanos());
755
756        if let Some(nanos) = nanos {
757            Ok(Some(nanos.try_into()?))
758        } else {
759            Ok(None)
760        }
761    }
762
763    fn set_first_byte_timeout(
764        &mut self,
765        opts: Resource<types::RequestOptions>,
766        duration: Option<types::Duration>,
767    ) -> wasmtime::Result<Result<(), ()>> {
768        self.table.get_mut(&opts)?.first_byte_timeout =
769            duration.map(std::time::Duration::from_nanos);
770        Ok(Ok(()))
771    }
772
773    fn between_bytes_timeout(
774        &mut self,
775        opts: Resource<types::RequestOptions>,
776    ) -> wasmtime::Result<Option<types::Duration>> {
777        let nanos = self
778            .table
779            .get(&opts)?
780            .between_bytes_timeout
781            .map(|d| d.as_nanos());
782
783        if let Some(nanos) = nanos {
784            Ok(Some(nanos.try_into()?))
785        } else {
786            Ok(None)
787        }
788    }
789
790    fn set_between_bytes_timeout(
791        &mut self,
792        opts: Resource<types::RequestOptions>,
793        duration: Option<types::Duration>,
794    ) -> wasmtime::Result<Result<(), ()>> {
795        self.table.get_mut(&opts)?.between_bytes_timeout =
796            duration.map(std::time::Duration::from_nanos);
797        Ok(Ok(()))
798    }
799
800    fn drop(&mut self, rep: Resource<types::RequestOptions>) -> wasmtime::Result<()> {
801        let _ = self.table.delete(rep)?;
802        Ok(())
803    }
804}