1use crate::FieldMap;
2use crate::p3::bindings::clocks::monotonic_clock::Duration;
3use crate::p3::bindings::http::types::{
4 ErrorCode, FieldName, FieldValue, Fields, HeaderError, Headers, Host, HostFields, HostRequest,
5 HostRequestOptions, HostRequestWithStore, HostResponse, HostResponseWithStore, Method, Request,
6 RequestOptions, RequestOptionsError, Response, Scheme, StatusCode, Trailers,
7};
8use crate::p3::body::{Body, HostBodyStreamProducer};
9use crate::p3::{HeaderResult, HttpError, RequestOptionsResult, WasiHttp, WasiHttpCtxView};
10use core::mem;
11use core::pin::Pin;
12use core::task::{Context, Poll, ready};
13use http::header::CONTENT_LENGTH;
14use std::sync::Arc;
15use tokio::sync::oneshot;
16use wasmtime::component::{
17 Access, FutureProducer, FutureReader, Resource, ResourceTable, StreamReader,
18};
19use wasmtime::error::Context as _;
20use wasmtime::{AsContextMut, StoreContextMut};
21
22fn get_fields<'a>(
23 table: &'a ResourceTable,
24 fields: &Resource<Fields>,
25) -> wasmtime::Result<&'a Fields> {
26 table
27 .get(&fields)
28 .context("failed to get fields from table")
29}
30
31fn get_fields_mut<'a>(
32 table: &'a mut ResourceTable,
33 fields: &Resource<Fields>,
34) -> HeaderResult<&'a mut Fields> {
35 table
36 .get_mut(&fields)
37 .context("failed to get fields from table")
38 .map_err(crate::p3::HeaderError::trap)
39}
40
41fn push_fields(table: &mut ResourceTable, fields: Fields) -> wasmtime::Result<Resource<Fields>> {
42 table.push(fields).context("failed to push fields to table")
43}
44
45fn delete_fields(table: &mut ResourceTable, fields: Resource<Fields>) -> wasmtime::Result<Fields> {
46 let mut fields = table
47 .delete(fields)
48 .context("failed to delete fields from table")?;
49 fields.set_immutable();
54 Ok(fields)
55}
56
57fn get_request<'a>(
58 table: &'a ResourceTable,
59 req: &Resource<Request>,
60) -> wasmtime::Result<&'a Request> {
61 table.get(req).context("failed to get request from table")
62}
63
64fn get_request_mut<'a>(
65 table: &'a mut ResourceTable,
66 req: &Resource<Request>,
67) -> wasmtime::Result<&'a mut Request> {
68 table
69 .get_mut(req)
70 .context("failed to get request from table")
71}
72
73fn get_response<'a>(
74 table: &'a ResourceTable,
75 res: &Resource<Response>,
76) -> wasmtime::Result<&'a Response> {
77 table.get(res).context("failed to get response from table")
78}
79
80fn get_response_mut<'a>(
81 table: &'a mut ResourceTable,
82 res: &Resource<Response>,
83) -> wasmtime::Result<&'a mut Response> {
84 table
85 .get_mut(res)
86 .context("failed to get response from table")
87}
88
89fn get_request_options<'a>(
90 table: &'a ResourceTable,
91 opts: &Resource<RequestOptions>,
92) -> wasmtime::Result<&'a RequestOptions> {
93 table
94 .get(opts)
95 .context("failed to get request options from table")
96}
97
98fn get_request_options_mut<'a>(
99 table: &'a mut ResourceTable,
100 opts: &Resource<RequestOptions>,
101) -> RequestOptionsResult<&'a mut RequestOptions> {
102 table
103 .get_mut(opts)
104 .context("failed to get request options from table")
105 .map_err(crate::p3::RequestOptionsError::trap)
106}
107
108fn push_request_options(
109 table: &mut ResourceTable,
110 opts: RequestOptions,
111) -> wasmtime::Result<Resource<RequestOptions>> {
112 table
113 .push(opts)
114 .context("failed to push request options to table")
115}
116
117fn delete_request_options(
118 table: &mut ResourceTable,
119 opts: Resource<RequestOptions>,
120) -> wasmtime::Result<RequestOptions> {
121 table
122 .delete(opts)
123 .context("failed to delete request options from table")
124}
125
126fn parse_header_value(
127 name: &http::HeaderName,
128 value: impl AsRef<[u8]>,
129) -> Result<http::HeaderValue, HeaderError> {
130 if name == CONTENT_LENGTH {
131 let s = str::from_utf8(value.as_ref()).or(Err(HeaderError::InvalidSyntax))?;
132 let v: u64 = s.parse().or(Err(HeaderError::InvalidSyntax))?;
133 Ok(v.into())
134 } else {
135 http::HeaderValue::from_bytes(value.as_ref()).or(Err(HeaderError::InvalidSyntax))
136 }
137}
138
139enum GuestBodyResultProducer {
140 Receiver(oneshot::Receiver<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
141 Future(Pin<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
142}
143
144fn poll_future<T>(
145 cx: &mut Context<'_>,
146 fut: Pin<&mut (impl Future<Output = T> + ?Sized)>,
147 finish: bool,
148) -> Poll<Option<T>> {
149 match fut.poll(cx) {
150 Poll::Ready(v) => Poll::Ready(Some(v)),
151 Poll::Pending if finish => Poll::Ready(None),
152 Poll::Pending => Poll::Pending,
153 }
154}
155
156impl<D> FutureProducer<D> for GuestBodyResultProducer {
157 type Item = Result<(), ErrorCode>;
158
159 fn poll_produce(
160 mut self: Pin<&mut Self>,
161 cx: &mut Context<'_>,
162 _: StoreContextMut<D>,
163 finish: bool,
164 ) -> Poll<wasmtime::Result<Option<Self::Item>>> {
165 match &mut *self {
166 Self::Receiver(rx) => {
167 match ready!(poll_future(cx, Pin::new(rx), finish)) {
168 Some(Ok(fut)) => {
169 let mut fut = Box::into_pin(fut);
170 let res = poll_future(cx, fut.as_mut(), finish);
172 *self = Self::Future(fut);
173 res.map(Ok)
174 }
175 Some(Err(..)) => {
176 Poll::Ready(Ok(Some(Ok(()))))
178 }
179 None => Poll::Ready(Ok(None)),
180 }
181 }
182 Self::Future(fut) => poll_future(cx, fut.as_mut(), finish).map(Ok),
183 }
184 }
185}
186
187impl HostFields for WasiHttpCtxView<'_> {
188 fn new(&mut self) -> wasmtime::Result<Resource<Fields>> {
189 push_fields(self.table, FieldMap::new_mutable(self.ctx.field_size_limit))
190 }
191
192 fn from_list(
193 &mut self,
194 entries: Vec<(FieldName, FieldValue)>,
195 ) -> HeaderResult<Resource<Fields>> {
196 let mut fields = FieldMap::new_mutable(self.ctx.field_size_limit);
197 for (name, value) in entries {
198 let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
199 if self.hooks.is_forbidden_header(&name) {
200 return Err(HeaderError::Forbidden.into());
201 }
202 let value = parse_header_value(&name, value)?;
203 fields.append(name, value)?;
204 }
205 let fields = push_fields(self.table, fields).map_err(crate::p3::HeaderError::trap)?;
206 Ok(fields)
207 }
208
209 fn get(
210 &mut self,
211 fields: Resource<Fields>,
212 name: FieldName,
213 ) -> wasmtime::Result<Vec<FieldValue>> {
214 let fields = get_fields(self.table, &fields)?;
215 Ok(fields
216 .get_all(name)
217 .into_iter()
218 .map(|val| val.as_bytes().into())
219 .collect())
220 }
221
222 fn has(&mut self, fields: Resource<Fields>, name: FieldName) -> wasmtime::Result<bool> {
223 let fields = get_fields(self.table, &fields)?;
224 Ok(fields.contains_key(name))
225 }
226
227 fn set(
228 &mut self,
229 fields: Resource<Fields>,
230 name: FieldName,
231 value: Vec<FieldValue>,
232 ) -> HeaderResult<()> {
233 let name = name.parse().map_err(|_| HeaderError::InvalidSyntax)?;
234 if self.hooks.is_forbidden_header(&name) {
235 return Err(HeaderError::Forbidden.into());
236 }
237 let mut values = Vec::with_capacity(value.len());
238 for value in value {
239 let value = parse_header_value(&name, value)?;
240 values.push(value);
241 }
242 get_fields_mut(self.table, &fields)?.set(name, values)?;
243 Ok(())
244 }
245
246 fn delete(&mut self, fields: Resource<Fields>, name: FieldName) -> HeaderResult<()> {
247 let name = name.parse().map_err(|_| HeaderError::InvalidSyntax)?;
248 if self.hooks.is_forbidden_header(&name) {
249 return Err(HeaderError::Forbidden.into());
250 }
251 get_fields_mut(self.table, &fields)?.remove_all(name)?;
252 Ok(())
253 }
254
255 fn get_and_delete(
256 &mut self,
257 fields: Resource<Fields>,
258 name: FieldName,
259 ) -> HeaderResult<Vec<FieldValue>> {
260 let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
261 if self.hooks.is_forbidden_header(&name) {
262 return Err(HeaderError::Forbidden.into());
263 }
264 let values = get_fields_mut(self.table, &fields)?
265 .remove_all(name)?
266 .into_iter();
267 Ok(values.map(|value| value.as_bytes().into()).collect())
268 }
269
270 fn append(
271 &mut self,
272 fields: Resource<Fields>,
273 name: FieldName,
274 value: FieldValue,
275 ) -> HeaderResult<()> {
276 let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
277 if self.hooks.is_forbidden_header(&name) {
278 return Err(HeaderError::Forbidden.into());
279 }
280 let value = parse_header_value(&name, value)?;
281 get_fields_mut(self.table, &fields)?.append(name, value)?;
282 Ok(())
283 }
284
285 fn copy_all(
286 &mut self,
287 fields: Resource<Fields>,
288 ) -> wasmtime::Result<Vec<(FieldName, FieldValue)>> {
289 let fields = get_fields(self.table, &fields)?;
290 let fields = fields
291 .iter()
292 .map(|(name, value)| (name.as_str().into(), value.as_bytes().into()))
293 .collect();
294 Ok(fields)
295 }
296
297 fn clone(&mut self, fields: Resource<Fields>) -> wasmtime::Result<Resource<Fields>> {
298 let mut fields = get_fields(self.table, &fields)?.clone();
299 fields.set_mutable(self.ctx.field_size_limit);
300 push_fields(self.table, fields)
301 }
302
303 fn drop(&mut self, fields: Resource<Fields>) -> wasmtime::Result<()> {
304 delete_fields(self.table, fields)?;
305 Ok(())
306 }
307}
308
309impl HostRequestWithStore for WasiHttp {
310 fn new<T>(
311 mut store: Access<T, Self>,
312 headers: Resource<Headers>,
313 contents: Option<StreamReader<u8>>,
314 trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
315 options: Option<Resource<RequestOptions>>,
316 ) -> wasmtime::Result<(Resource<Request>, FutureReader<Result<(), ErrorCode>>)> {
317 let (result_tx, result_rx) = oneshot::channel();
318 let body = match contents
319 .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
320 {
321 Some(Ok(mut producer)) => Body::Host {
322 body: mem::take(&mut producer.body),
323 result_tx,
324 },
325 Some(Err(rx)) => Body::Guest {
326 contents_rx: Some(rx),
327 trailers_rx: trailers,
328 result_tx,
329 },
330 None => Body::Guest {
331 contents_rx: None,
332 trailers_rx: trailers,
333 result_tx,
334 },
335 };
336 let WasiHttpCtxView { table, .. } = store.get();
337 let headers = delete_fields(table, headers)?;
338 let options = options
339 .map(|options| delete_request_options(table, options))
340 .transpose()?;
341 let req = Request {
342 method: http::Method::GET,
343 scheme: None,
344 authority: None,
345 path_with_query: None,
346 headers,
347 options: options.map(Into::into),
348 body,
349 };
350 let req = table.push(req).context("failed to push request to table")?;
351 Ok((
352 req,
353 FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx))?,
354 ))
355 }
356
357 fn consume_body<T>(
358 mut store: Access<T, Self>,
359 req: Resource<Request>,
360 fut: FutureReader<Result<(), ErrorCode>>,
361 ) -> wasmtime::Result<(
362 StreamReader<u8>,
363 FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
364 )> {
365 let getter = store.getter();
366 let Request { body, .. } = store
367 .get()
368 .table
369 .delete(req)
370 .context("failed to delete request from table")?;
371 body.consume(store, fut, getter)
372 }
373
374 fn drop<T>(mut store: Access<'_, T, Self>, req: Resource<Request>) -> wasmtime::Result<()> {
375 let Request { body, .. } = store
376 .get()
377 .table
378 .delete(req)
379 .context("failed to delete request from table")?;
380 body.drop(store)?;
381 Ok(())
382 }
383}
384
385impl HostRequest for WasiHttpCtxView<'_> {
386 fn get_method(&mut self, req: Resource<Request>) -> wasmtime::Result<Method> {
387 let Request { method, .. } = get_request(self.table, &req)?;
388 Ok(method.into())
389 }
390
391 fn set_method(
392 &mut self,
393 req: Resource<Request>,
394 method: Method,
395 ) -> wasmtime::Result<Result<(), ()>> {
396 let req = get_request_mut(self.table, &req)?;
397 let Ok(method) = method.try_into() else {
398 return Ok(Err(()));
399 };
400 req.method = method;
401 Ok(Ok(()))
402 }
403
404 fn get_path_with_query(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
405 let Request {
406 path_with_query, ..
407 } = get_request(self.table, &req)?;
408 Ok(path_with_query.as_ref().map(|pq| pq.as_str().into()))
409 }
410
411 fn set_path_with_query(
412 &mut self,
413 req: Resource<Request>,
414 path_with_query: Option<String>,
415 ) -> wasmtime::Result<Result<(), ()>> {
416 let req = get_request_mut(self.table, &req)?;
417 let Some(path_with_query) = path_with_query else {
418 req.path_with_query = None;
419 return Ok(Ok(()));
420 };
421 let Ok(path_with_query) = path_with_query.try_into() else {
422 return Ok(Err(()));
423 };
424 req.path_with_query = Some(path_with_query);
425 Ok(Ok(()))
426 }
427
428 fn get_scheme(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<Scheme>> {
429 let Request { scheme, .. } = get_request(self.table, &req)?;
430 Ok(scheme.as_ref().map(Into::into))
431 }
432
433 fn set_scheme(
434 &mut self,
435 req: Resource<Request>,
436 scheme: Option<Scheme>,
437 ) -> wasmtime::Result<Result<(), ()>> {
438 let req = get_request_mut(self.table, &req)?;
439 let Some(scheme) = scheme else {
440 req.scheme = None;
441 return Ok(Ok(()));
442 };
443 let Ok(scheme) = scheme.try_into() else {
444 return Ok(Err(()));
445 };
446 req.scheme = Some(scheme);
447 Ok(Ok(()))
448 }
449
450 fn get_authority(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
451 let Request { authority, .. } = get_request(self.table, &req)?;
452 Ok(authority.as_ref().map(|auth| auth.as_str().into()))
453 }
454
455 fn set_authority(
456 &mut self,
457 req: Resource<Request>,
458 authority: Option<String>,
459 ) -> wasmtime::Result<Result<(), ()>> {
460 let req = get_request_mut(self.table, &req)?;
461 let Some(authority) = authority else {
462 req.authority = None;
463 return Ok(Ok(()));
464 };
465 let has_port = authority.contains(':');
466 let Ok(authority) = http::uri::Authority::try_from(authority) else {
467 return Ok(Err(()));
468 };
469 if has_port && authority.port_u16().is_none() {
470 return Ok(Err(()));
471 }
472 req.authority = Some(authority);
473 Ok(Ok(()))
474 }
475
476 fn get_options(
477 &mut self,
478 req: Resource<Request>,
479 ) -> wasmtime::Result<Option<Resource<RequestOptions>>> {
480 let Request { options, .. } = get_request(self.table, &req)?;
481 if let Some(options) = options {
482 let options = push_request_options(
483 self.table,
484 RequestOptions::new_immutable(Arc::clone(options)),
485 )?;
486 Ok(Some(options))
487 } else {
488 Ok(None)
489 }
490 }
491
492 fn get_headers(&mut self, req: Resource<Request>) -> wasmtime::Result<Resource<Headers>> {
493 let Request { headers, .. } = get_request(self.table, &req)?;
494 push_fields(self.table, headers.clone())
495 }
496}
497
498impl HostRequestOptions for WasiHttpCtxView<'_> {
499 fn new(&mut self) -> wasmtime::Result<Resource<RequestOptions>> {
500 push_request_options(self.table, RequestOptions::new_mutable_default())
501 }
502
503 fn get_connect_timeout(
504 &mut self,
505 opts: Resource<RequestOptions>,
506 ) -> wasmtime::Result<Option<Duration>> {
507 let opts = get_request_options(self.table, &opts)?;
508 let Some(connect_timeout) = opts.connect_timeout else {
509 return Ok(None);
510 };
511 let ns = connect_timeout.as_nanos();
512 let ns = Duration::try_from(ns)
513 .context("connect timeout duration nanoseconds do not fit in u64")?;
514 Ok(Some(ns))
515 }
516
517 fn set_connect_timeout(
518 &mut self,
519 opts: Resource<RequestOptions>,
520 duration: Option<Duration>,
521 ) -> RequestOptionsResult<()> {
522 let opts = get_request_options_mut(self.table, &opts)?;
523 let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
524 opts.connect_timeout = duration.map(core::time::Duration::from_nanos);
525 Ok(())
526 }
527
528 fn get_first_byte_timeout(
529 &mut self,
530 opts: Resource<RequestOptions>,
531 ) -> wasmtime::Result<Option<Duration>> {
532 let opts = get_request_options(self.table, &opts)?;
533 let Some(first_byte_timeout) = opts.first_byte_timeout else {
534 return Ok(None);
535 };
536 let ns = first_byte_timeout.as_nanos();
537 let ns = Duration::try_from(ns)
538 .context("first byte timeout duration nanoseconds do not fit in u64")?;
539 Ok(Some(ns))
540 }
541
542 fn set_first_byte_timeout(
543 &mut self,
544 opts: Resource<RequestOptions>,
545 duration: Option<Duration>,
546 ) -> RequestOptionsResult<()> {
547 let opts = get_request_options_mut(self.table, &opts)?;
548 let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
549 opts.first_byte_timeout = duration.map(core::time::Duration::from_nanos);
550 Ok(())
551 }
552
553 fn get_between_bytes_timeout(
554 &mut self,
555 opts: Resource<RequestOptions>,
556 ) -> wasmtime::Result<Option<Duration>> {
557 let opts = get_request_options(self.table, &opts)?;
558 let Some(between_bytes_timeout) = opts.between_bytes_timeout else {
559 return Ok(None);
560 };
561 let ns = between_bytes_timeout.as_nanos();
562 let ns = Duration::try_from(ns)
563 .context("between bytes timeout duration nanoseconds do not fit in u64")?;
564 Ok(Some(ns))
565 }
566
567 fn set_between_bytes_timeout(
568 &mut self,
569 opts: Resource<RequestOptions>,
570 duration: Option<Duration>,
571 ) -> RequestOptionsResult<()> {
572 let opts = get_request_options_mut(self.table, &opts)?;
573 let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
574 opts.between_bytes_timeout = duration.map(core::time::Duration::from_nanos);
575 Ok(())
576 }
577
578 fn clone(
579 &mut self,
580 opts: Resource<RequestOptions>,
581 ) -> wasmtime::Result<Resource<RequestOptions>> {
582 let opts = get_request_options(self.table, &opts)?;
583 push_request_options(self.table, RequestOptions::new_mutable(Arc::clone(opts)))
584 }
585
586 fn drop(&mut self, opts: Resource<RequestOptions>) -> wasmtime::Result<()> {
587 delete_request_options(self.table, opts)?;
588 Ok(())
589 }
590}
591
592impl HostResponseWithStore for WasiHttp {
593 fn new<T>(
594 mut store: Access<T, Self>,
595 headers: Resource<Headers>,
596 contents: Option<StreamReader<u8>>,
597 trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
598 ) -> wasmtime::Result<(Resource<Response>, FutureReader<Result<(), ErrorCode>>)> {
599 let (result_tx, result_rx) = oneshot::channel();
600 let body = match contents
601 .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
602 {
603 Some(Ok(mut producer)) => Body::Host {
604 body: mem::take(&mut producer.body),
605 result_tx,
606 },
607 Some(Err(rx)) => Body::Guest {
608 contents_rx: Some(rx),
609 trailers_rx: trailers,
610 result_tx,
611 },
612 None => Body::Guest {
613 contents_rx: None,
614 trailers_rx: trailers,
615 result_tx,
616 },
617 };
618 let WasiHttpCtxView { table, .. } = store.get();
619 let headers = delete_fields(table, headers)?;
620 let res = Response {
621 status: http::StatusCode::OK,
622 headers,
623 body,
624 };
625 let res = table
626 .push(res)
627 .context("failed to push response to table")?;
628 Ok((
629 res,
630 FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx))?,
631 ))
632 }
633
634 fn consume_body<T>(
635 mut store: Access<T, Self>,
636 res: Resource<Response>,
637 fut: FutureReader<Result<(), ErrorCode>>,
638 ) -> wasmtime::Result<(
639 StreamReader<u8>,
640 FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
641 )> {
642 let getter = store.getter();
643 let Response { body, .. } = store
644 .get()
645 .table
646 .delete(res)
647 .context("failed to delete response from table")?;
648 body.consume(store, fut, getter)
649 }
650
651 fn drop<T>(mut store: Access<'_, T, Self>, res: Resource<Response>) -> wasmtime::Result<()> {
652 let Response { body, .. } = store
653 .get()
654 .table
655 .delete(res)
656 .context("failed to delete response from table")?;
657 body.drop(store)?;
658 Ok(())
659 }
660}
661
662impl HostResponse for WasiHttpCtxView<'_> {
663 fn get_status_code(&mut self, res: Resource<Response>) -> wasmtime::Result<StatusCode> {
664 let res = get_response(self.table, &res)?;
665 Ok(res.status.into())
666 }
667
668 fn set_status_code(
669 &mut self,
670 res: Resource<Response>,
671 status_code: StatusCode,
672 ) -> wasmtime::Result<Result<(), ()>> {
673 let res = get_response_mut(self.table, &res)?;
674 match http::StatusCode::from_u16(status_code) {
675 Ok(status) if matches!(status_code, 100..=599) => {
676 res.status = status;
677 Ok(Ok(()))
678 }
679 _ => Ok(Err(())),
680 }
681 }
682
683 fn get_headers(&mut self, res: Resource<Response>) -> wasmtime::Result<Resource<Headers>> {
684 let Response { headers, .. } = get_response(self.table, &res)?;
685 push_fields(self.table, headers.clone())
686 }
687}
688
689impl Host for WasiHttpCtxView<'_> {
690 fn convert_error_code(&mut self, error: HttpError) -> wasmtime::Result<ErrorCode> {
691 error.downcast()
692 }
693
694 fn convert_header_error(
695 &mut self,
696 error: crate::p3::HeaderError,
697 ) -> wasmtime::Result<HeaderError> {
698 error.downcast()
699 }
700
701 fn convert_request_options_error(
702 &mut self,
703 error: crate::p3::RequestOptionsError,
704 ) -> wasmtime::Result<RequestOptionsError> {
705 error.downcast()
706 }
707}