1use 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 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 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 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 #[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}