Skip to main content

wasmtime_wasi_http/p2/
error.rs

1use crate::p2::bindings::http::types::{self, ErrorCode};
2use crate::{Error, FieldMapError, WasiHttpHooks};
3use std::error::Error as _;
4use std::fmt;
5use std::io::ErrorKind;
6use wasmtime::component::ResourceTableError;
7
8/// A [`Result`] type where the error type defaults to [`HttpError`].
9pub type HttpResult<T, E = HttpError> = Result<T, E>;
10
11/// A `wasi:http`-specific error type used to represent either a trap or an
12/// [`ErrorCode`].
13///
14/// Modeled after [`TrappableError`](wasmtime_wasi::TrappableError).
15#[repr(transparent)]
16pub struct HttpError {
17    err: wasmtime::Error,
18}
19
20impl HttpError {
21    /// Create a new `HttpError` that represents a trap.
22    pub fn trap(err: impl Into<wasmtime::Error>) -> HttpError {
23        HttpError { err: err.into() }
24    }
25
26    /// Downcast this error to an [`ErrorCode`].
27    pub fn downcast(self) -> wasmtime::Result<ErrorCode> {
28        self.err.downcast()
29    }
30
31    /// Downcast this error to a reference to an [`ErrorCode`]
32    pub fn downcast_ref(&self) -> Option<&ErrorCode> {
33        self.err.downcast_ref()
34    }
35}
36
37impl From<ErrorCode> for HttpError {
38    fn from(error: ErrorCode) -> Self {
39        Self { err: error.into() }
40    }
41}
42
43impl From<ResourceTableError> for HttpError {
44    fn from(error: ResourceTableError) -> Self {
45        HttpError::trap(error)
46    }
47}
48
49impl fmt::Debug for HttpError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        self.err.fmt(f)
52    }
53}
54
55impl fmt::Display for HttpError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        self.err.fmt(f)
58    }
59}
60
61impl std::error::Error for HttpError {}
62
63/// A [`Result`] type where the error type defaults to [`HeaderError`].
64pub type HeaderResult<T, E = HeaderError> = Result<T, E>;
65
66/// A `wasi:http`-specific error type used to represent either a trap or an
67/// [`types::HeaderError`].
68///
69/// Modeled after [`TrappableError`](wasmtime_wasi::TrappableError).
70#[repr(transparent)]
71pub struct HeaderError {
72    err: wasmtime::Error,
73}
74
75impl HeaderError {
76    /// Create a new `HeaderError` that represents a trap.
77    pub fn trap(err: impl Into<wasmtime::Error>) -> HeaderError {
78        HeaderError { err: err.into() }
79    }
80
81    /// Downcast this error to an [`ErrorCode`].
82    pub fn downcast(self) -> wasmtime::Result<types::HeaderError> {
83        self.err.downcast()
84    }
85
86    /// Downcast this error to a reference to an [`ErrorCode`]
87    pub fn downcast_ref(&self) -> Option<&types::HeaderError> {
88        self.err.downcast_ref()
89    }
90}
91
92impl From<types::HeaderError> for HeaderError {
93    fn from(error: types::HeaderError) -> Self {
94        Self { err: error.into() }
95    }
96}
97
98impl From<ResourceTableError> for HeaderError {
99    fn from(error: ResourceTableError) -> Self {
100        HeaderError::trap(error)
101    }
102}
103
104impl From<http::header::InvalidHeaderName> for HeaderError {
105    fn from(_: http::header::InvalidHeaderName) -> Self {
106        HeaderError::from(types::HeaderError::InvalidSyntax)
107    }
108}
109
110impl From<http::header::InvalidHeaderValue> for HeaderError {
111    fn from(_: http::header::InvalidHeaderValue) -> Self {
112        HeaderError::from(types::HeaderError::InvalidSyntax)
113    }
114}
115
116impl From<FieldMapError> for HeaderError {
117    fn from(err: FieldMapError) -> Self {
118        match err {
119            FieldMapError::Immutable => types::HeaderError::Immutable.into(),
120            FieldMapError::InvalidHeaderName | FieldMapError::InvalidHeaderValue => {
121                types::HeaderError::InvalidSyntax.into()
122            }
123            FieldMapError::TooManyFields | FieldMapError::TotalSizeTooBig => HeaderError::trap(err),
124            FieldMapError::Forbidden => types::HeaderError::Forbidden.into(),
125        }
126    }
127}
128
129impl fmt::Debug for HeaderError {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        self.err.fmt(f)
132    }
133}
134
135impl fmt::Display for HeaderError {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        self.err.fmt(f)
138    }
139}
140
141pub(crate) fn internal_error(msg: String) -> ErrorCode {
142    ErrorCode::InternalError(Some(msg))
143}
144
145/// Translate a [`http::Error`] to a wasi-http `ErrorCode` in the context of a request.
146pub fn http_request_error(err: http::Error) -> ErrorCode {
147    if err.is::<http::uri::InvalidUri>() {
148        return ErrorCode::HttpRequestUriInvalid;
149    }
150
151    tracing::warn!("http request error: {err:?}");
152
153    ErrorCode::HttpProtocolError
154}
155
156/// Translate a [`hyper::Error`] to a wasi-http `ErrorCode` in the context of a request.
157pub fn hyper_request_error(err: hyper::Error) -> ErrorCode {
158    // If there's a source, we might be able to extract a wasi-http error from it.
159    if let Some(cause) = err.source() {
160        if let Some(err) = cause.downcast_ref::<ErrorCode>() {
161            return err.clone();
162        }
163    }
164
165    tracing::warn!("hyper request error: {err:?}");
166
167    ErrorCode::HttpProtocolError
168}
169
170/// Translate a [`hyper::Error`] to a wasi-http `ErrorCode` in the context of a response.
171pub fn hyper_response_error(err: hyper::Error) -> ErrorCode {
172    if err.is_timeout() {
173        return ErrorCode::HttpResponseTimeout;
174    }
175
176    // If there's a source, we might be able to extract a wasi-http error from it.
177    if let Some(cause) = err.source() {
178        if let Some(err) = cause.downcast_ref::<ErrorCode>() {
179            return err.clone();
180        }
181    }
182
183    tracing::warn!("hyper response error: {err:?}");
184
185    ErrorCode::HttpProtocolError
186}
187
188impl From<hyper::Error> for ErrorCode {
189    fn from(err: hyper::Error) -> Self {
190        hyper_response_error(err)
191    }
192}
193
194impl From<ErrorCode> for Error {
195    fn from(e: ErrorCode) -> Self {
196        match e {
197            ErrorCode::DnsTimeout => Self::DnsTimeout,
198            ErrorCode::DnsError(payload) => Self::DnsError {
199                rcode: payload.rcode,
200                info_code: payload.info_code,
201            },
202            ErrorCode::DestinationNotFound => Self::DestinationNotFound,
203            ErrorCode::DestinationUnavailable => Self::DestinationUnavailable,
204            ErrorCode::DestinationIpProhibited => Self::DestinationIpProhibited,
205            ErrorCode::DestinationIpUnroutable => Self::DestinationIpUnroutable,
206            ErrorCode::ConnectionRefused => Self::ConnectionRefused,
207            ErrorCode::ConnectionTerminated => Self::ConnectionTerminated,
208            ErrorCode::ConnectionTimeout => Self::ConnectionTimeout,
209            ErrorCode::ConnectionReadTimeout => Self::ConnectionReadTimeout,
210            ErrorCode::ConnectionWriteTimeout => Self::ConnectionWriteTimeout,
211            ErrorCode::ConnectionLimitReached => Self::ConnectionLimitReached,
212            ErrorCode::TlsProtocolError => Self::TlsProtocolError,
213            ErrorCode::TlsCertificateError => Self::TlsCertificateError,
214            ErrorCode::TlsAlertReceived(payload) => Self::TlsAlertReceived {
215                alert_id: payload.alert_id,
216                alert_message: payload.alert_message,
217            },
218            ErrorCode::HttpRequestDenied => Self::HttpRequestDenied,
219            ErrorCode::HttpRequestLengthRequired => Self::HttpRequestLengthRequired,
220            ErrorCode::HttpRequestBodySize(payload) => Self::HttpRequestBodySize(payload),
221            ErrorCode::HttpRequestMethodInvalid => Self::HttpRequestMethodInvalid,
222            ErrorCode::HttpRequestUriInvalid => Self::HttpRequestUriInvalid,
223            ErrorCode::HttpRequestUriTooLong => Self::HttpRequestUriTooLong,
224            ErrorCode::HttpRequestHeaderSectionSize(payload) => {
225                Self::HttpRequestHeaderSectionSize(payload)
226            }
227            ErrorCode::HttpRequestHeaderSize(payload) => {
228                let (field_name, field_size) = match payload {
229                    Some(p) => (p.field_name, p.field_size),
230                    None => (None, None),
231                };
232                Self::HttpRequestHeaderSize {
233                    field_name,
234                    field_size,
235                }
236            }
237            ErrorCode::HttpRequestTrailerSectionSize(payload) => {
238                Self::HttpRequestTrailerSectionSize(payload)
239            }
240            ErrorCode::HttpRequestTrailerSize(payload) => Self::HttpRequestTrailerSize {
241                field_name: payload.field_name,
242                field_size: payload.field_size,
243            },
244            ErrorCode::HttpResponseIncomplete => Self::HttpResponseIncomplete,
245            ErrorCode::HttpResponseHeaderSectionSize(payload) => {
246                Self::HttpResponseHeaderSectionSize(payload)
247            }
248            ErrorCode::HttpResponseHeaderSize(payload) => Self::HttpResponseHeaderSize {
249                field_name: payload.field_name,
250                field_size: payload.field_size,
251            },
252            ErrorCode::HttpResponseBodySize(payload) => Self::HttpResponseBodySize(payload),
253            ErrorCode::HttpResponseTrailerSectionSize(payload) => {
254                Self::HttpResponseTrailerSectionSize(payload)
255            }
256            ErrorCode::HttpResponseTrailerSize(payload) => Self::HttpResponseTrailerSize {
257                field_name: payload.field_name,
258                field_size: payload.field_size,
259            },
260            ErrorCode::HttpResponseTransferCoding(payload) => {
261                Self::HttpResponseTransferCoding(payload)
262            }
263            ErrorCode::HttpResponseContentCoding(payload) => {
264                Self::HttpResponseContentCoding(payload)
265            }
266            ErrorCode::HttpResponseTimeout => Self::HttpResponseTimeout,
267            ErrorCode::HttpUpgradeFailed => Self::HttpUpgradeFailed,
268            ErrorCode::HttpProtocolError => Self::HttpProtocolError,
269            ErrorCode::LoopDetected => Self::LoopDetected,
270            ErrorCode::ConfigurationError => Self::ConfigurationError,
271            ErrorCode::InternalError(payload) => Self::InternalError(payload),
272        }
273    }
274}
275
276impl dyn WasiHttpHooks + '_ {
277    pub(crate) fn error_to_p2(&mut self, e: &Error) -> ErrorCode {
278        match e {
279            Error::Hyper(err) => {
280                // If there's a source, we might be able to extract a wasi-http error from it.
281                if let Some(cause) = err.source() {
282                    if let Some(err) = cause.downcast_ref::<ErrorCode>() {
283                        return err.clone();
284                    }
285                    if let Some(err) = cause.downcast_ref::<Error>() {
286                        return self.error_to_p2(err);
287                    }
288                }
289
290                self.p2_error_from_hyper(err)
291            }
292            Error::Connect(err) => {
293                if err.kind() == ErrorKind::AddrNotAvailable {
294                    return ErrorCode::DnsError(types::DnsErrorPayload {
295                        rcode: Some("address not available".to_string()),
296                        info_code: None,
297                    });
298                }
299
300                if err
301                    .to_string()
302                    .starts_with("failed to lookup address information")
303                {
304                    return ErrorCode::DnsError(types::DnsErrorPayload {
305                        rcode: Some("address not available".to_string()),
306                        info_code: None,
307                    });
308                }
309
310                self.p2_error_from_connect(err)
311            }
312            Error::Tls(err) => self.p2_error_from_tls(err),
313            #[cfg(feature = "default-send-request")]
314            Error::InvalidDnsNameError(err) => self.p2_error_from_dns(err),
315            Error::DnsTimeout => ErrorCode::DnsTimeout,
316            Error::DnsError { rcode, info_code } => ErrorCode::DnsError(types::DnsErrorPayload {
317                rcode: rcode.clone(),
318                info_code: *info_code,
319            }),
320            Error::DestinationNotFound => ErrorCode::DestinationNotFound,
321            Error::DestinationUnavailable => ErrorCode::DestinationUnavailable,
322            Error::DestinationIpProhibited => ErrorCode::DestinationIpProhibited,
323            Error::DestinationIpUnroutable => ErrorCode::DestinationIpUnroutable,
324            Error::ConnectionRefused => ErrorCode::ConnectionRefused,
325            Error::ConnectionTerminated => ErrorCode::ConnectionTerminated,
326            Error::ConnectionTimeout => ErrorCode::ConnectionTimeout,
327            Error::ConnectionReadTimeout => ErrorCode::ConnectionReadTimeout,
328            Error::ConnectionWriteTimeout => ErrorCode::ConnectionWriteTimeout,
329            Error::ConnectionLimitReached => ErrorCode::ConnectionLimitReached,
330            Error::TlsProtocolError => ErrorCode::TlsProtocolError,
331            Error::TlsCertificateError => ErrorCode::TlsCertificateError,
332            Error::TlsAlertReceived {
333                alert_id,
334                alert_message,
335            } => ErrorCode::TlsAlertReceived(types::TlsAlertReceivedPayload {
336                alert_id: *alert_id,
337                alert_message: alert_message.clone(),
338            }),
339            Error::HttpRequestDenied => ErrorCode::HttpRequestDenied,
340            Error::HttpRequestLengthRequired => ErrorCode::HttpRequestLengthRequired,
341            Error::HttpRequestBodySize(payload) => ErrorCode::HttpRequestBodySize(*payload),
342            Error::HttpRequestMethodInvalid => ErrorCode::HttpRequestMethodInvalid,
343            Error::HttpRequestUriInvalid => ErrorCode::HttpRequestUriInvalid,
344            Error::HttpRequestUriTooLong => ErrorCode::HttpRequestUriTooLong,
345            Error::HttpRequestHeaderSectionSize(payload) => {
346                ErrorCode::HttpRequestHeaderSectionSize(*payload)
347            }
348            Error::HttpRequestHeaderSize {
349                field_name,
350                field_size,
351            } => ErrorCode::HttpRequestHeaderSize(Some(types::FieldSizePayload {
352                field_name: field_name.clone(),
353                field_size: *field_size,
354            })),
355            Error::HttpRequestTrailerSectionSize(payload) => {
356                ErrorCode::HttpRequestTrailerSectionSize(*payload)
357            }
358            Error::HttpRequestTrailerSize {
359                field_name,
360                field_size,
361            } => ErrorCode::HttpRequestTrailerSize(types::FieldSizePayload {
362                field_name: field_name.clone(),
363                field_size: *field_size,
364            }),
365            Error::HttpResponseIncomplete => ErrorCode::HttpResponseIncomplete,
366            Error::HttpResponseHeaderSectionSize(payload) => {
367                ErrorCode::HttpResponseHeaderSectionSize(*payload)
368            }
369            Error::HttpResponseHeaderSize {
370                field_name,
371                field_size,
372            } => ErrorCode::HttpResponseHeaderSize(types::FieldSizePayload {
373                field_name: field_name.clone(),
374                field_size: *field_size,
375            }),
376            Error::HttpResponseBodySize(payload) => ErrorCode::HttpResponseBodySize(*payload),
377            Error::HttpResponseTrailerSectionSize(payload) => {
378                ErrorCode::HttpResponseTrailerSectionSize(*payload)
379            }
380            Error::HttpResponseTrailerSize {
381                field_name,
382                field_size,
383            } => ErrorCode::HttpResponseTrailerSize(types::FieldSizePayload {
384                field_name: field_name.clone(),
385                field_size: *field_size,
386            }),
387            Error::HttpResponseTransferCoding(payload) => {
388                ErrorCode::HttpResponseTransferCoding(payload.clone())
389            }
390            Error::HttpResponseContentCoding(payload) => {
391                ErrorCode::HttpResponseContentCoding(payload.clone())
392            }
393            Error::HttpResponseTimeout => ErrorCode::HttpResponseTimeout,
394            Error::HttpUpgradeFailed => ErrorCode::HttpUpgradeFailed,
395            Error::HttpProtocolError => ErrorCode::HttpProtocolError,
396            Error::LoopDetected => ErrorCode::LoopDetected,
397            Error::ConfigurationError => ErrorCode::ConfigurationError,
398            Error::InternalError(payload) => ErrorCode::InternalError(payload.clone()),
399        }
400    }
401}