Skip to main content

wasmtime_wasi_http/p2/
error.rs

1use crate::p2::bindings::http::types::{self, ErrorCode};
2use crate::{Error, FieldMapError, WasiHttpCtxView};
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::HttpRequestHeaderSize {
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 WasiHttpCtxView<'_> {
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                }
286
287                self.hooks.p2_error_from_hyper(&err)
288            }
289            Error::Connect(err) => {
290                if err.kind() == ErrorKind::AddrNotAvailable {
291                    return ErrorCode::DnsError(types::DnsErrorPayload {
292                        rcode: Some("address not available".to_string()),
293                        info_code: None,
294                    });
295                }
296
297                if err
298                    .to_string()
299                    .starts_with("failed to lookup address information")
300                {
301                    return ErrorCode::DnsError(types::DnsErrorPayload {
302                        rcode: Some("address not available".to_string()),
303                        info_code: None,
304                    });
305                }
306
307                self.hooks.p2_error_from_connect(&err)
308            }
309            Error::Tls(err) => self.hooks.p2_error_from_tls(&err),
310            #[cfg(feature = "default-send-request")]
311            Error::InvalidDnsNameError(err) => self.hooks.p2_error_from_dns(&err),
312            Error::DnsTimeout => ErrorCode::DnsTimeout,
313            Error::DnsError { rcode, info_code } => {
314                ErrorCode::DnsError(types::DnsErrorPayload { rcode, info_code })
315            }
316            Error::DestinationNotFound => ErrorCode::DestinationNotFound,
317            Error::DestinationUnavailable => ErrorCode::DestinationUnavailable,
318            Error::DestinationIpProhibited => ErrorCode::DestinationIpProhibited,
319            Error::DestinationIpUnroutable => ErrorCode::DestinationIpUnroutable,
320            Error::ConnectionRefused => ErrorCode::ConnectionRefused,
321            Error::ConnectionTerminated => ErrorCode::ConnectionTerminated,
322            Error::ConnectionTimeout => ErrorCode::ConnectionTimeout,
323            Error::ConnectionReadTimeout => ErrorCode::ConnectionReadTimeout,
324            Error::ConnectionWriteTimeout => ErrorCode::ConnectionWriteTimeout,
325            Error::ConnectionLimitReached => ErrorCode::ConnectionLimitReached,
326            Error::TlsProtocolError => ErrorCode::TlsProtocolError,
327            Error::TlsCertificateError => ErrorCode::TlsCertificateError,
328            Error::TlsAlertReceived {
329                alert_id,
330                alert_message,
331            } => ErrorCode::TlsAlertReceived(types::TlsAlertReceivedPayload {
332                alert_id,
333                alert_message,
334            }),
335            Error::HttpRequestDenied => ErrorCode::HttpRequestDenied,
336            Error::HttpRequestLengthRequired => ErrorCode::HttpRequestLengthRequired,
337            Error::HttpRequestBodySize(payload) => ErrorCode::HttpRequestBodySize(payload),
338            Error::HttpRequestMethodInvalid => ErrorCode::HttpRequestMethodInvalid,
339            Error::HttpRequestUriInvalid => ErrorCode::HttpRequestUriInvalid,
340            Error::HttpRequestUriTooLong => ErrorCode::HttpRequestUriTooLong,
341            Error::HttpRequestHeaderSectionSize(payload) => {
342                ErrorCode::HttpRequestHeaderSectionSize(payload)
343            }
344            Error::HttpRequestHeaderSize {
345                field_name,
346                field_size,
347            } => ErrorCode::HttpRequestHeaderSize(Some(types::FieldSizePayload {
348                field_name,
349                field_size,
350            })),
351            Error::HttpRequestTrailerSectionSize(payload) => {
352                ErrorCode::HttpRequestTrailerSectionSize(payload)
353            }
354            Error::HttpRequestTrailerSize {
355                field_name,
356                field_size,
357            } => ErrorCode::HttpRequestTrailerSize(types::FieldSizePayload {
358                field_name,
359                field_size,
360            }),
361            Error::HttpResponseIncomplete => ErrorCode::HttpResponseIncomplete,
362            Error::HttpResponseHeaderSectionSize(payload) => {
363                ErrorCode::HttpResponseHeaderSectionSize(payload)
364            }
365            Error::HttpResponseHeaderSize {
366                field_name,
367                field_size,
368            } => ErrorCode::HttpResponseHeaderSize(types::FieldSizePayload {
369                field_name,
370                field_size,
371            }),
372            Error::HttpResponseBodySize(payload) => ErrorCode::HttpResponseBodySize(payload),
373            Error::HttpResponseTrailerSectionSize(payload) => {
374                ErrorCode::HttpResponseTrailerSectionSize(payload)
375            }
376            Error::HttpResponseTrailerSize {
377                field_name,
378                field_size,
379            } => ErrorCode::HttpResponseTrailerSize(types::FieldSizePayload {
380                field_name,
381                field_size,
382            }),
383            Error::HttpResponseTransferCoding(payload) => {
384                ErrorCode::HttpResponseTransferCoding(payload)
385            }
386            Error::HttpResponseContentCoding(payload) => {
387                ErrorCode::HttpResponseContentCoding(payload)
388            }
389            Error::HttpResponseTimeout => ErrorCode::HttpResponseTimeout,
390            Error::HttpUpgradeFailed => ErrorCode::HttpUpgradeFailed,
391            Error::HttpProtocolError => ErrorCode::HttpProtocolError,
392            Error::LoopDetected => ErrorCode::LoopDetected,
393            Error::ConfigurationError => ErrorCode::ConfigurationError,
394            Error::InternalError(payload) => ErrorCode::InternalError(payload),
395        }
396    }
397}