1#![cfg_attr(target_arch = "wasm32", allow(unused))]
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5
6use crate::{StatusCode, Url};
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11pub struct Error {
17 inner: Box<Inner>,
18}
19
20pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
21
22struct Inner {
23 kind: Kind,
24 source: Option<BoxError>,
25 url: Option<Url>,
26}
27
28impl Error {
29 pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
30 where
31 E: Into<BoxError>,
32 {
33 Error {
34 inner: Box::new(Inner {
35 kind,
36 source: source.map(Into::into),
37 url: None,
38 }),
39 }
40 }
41
42 pub fn url(&self) -> Option<&Url> {
44 self.inner.url.as_ref()
45 }
46
47 pub fn url_mut(&mut self) -> Option<&mut Url> {
53 self.inner.url.as_mut()
54 }
55
56 pub fn with_url(mut self, url: Url) -> Self {
58 self.inner.url = Some(url);
59 self
60 }
61
62 pub fn without_url(mut self) -> Self {
65 self.inner.url = None;
66 self
67 }
68
69 pub fn is_builder(&self) -> bool {
71 matches!(self.inner.kind, Kind::Builder)
72 }
73
74 pub fn is_redirect(&self) -> bool {
76 matches!(self.inner.kind, Kind::Redirect)
77 }
78
79 pub fn is_status(&self) -> bool {
81 matches!(self.inner.kind, Kind::Status(_))
82 }
83
84 pub fn is_timeout(&self) -> bool {
86 let mut source = self.source();
87
88 while let Some(err) = source {
89 if err.is::<TimedOut>() {
90 return true;
91 }
92 if let Some(io) = err.downcast_ref::<io::Error>() {
93 if io.kind() == io::ErrorKind::TimedOut {
94 return true;
95 }
96 }
97 source = err.source();
98 }
99
100 false
101 }
102
103 pub fn is_request(&self) -> bool {
105 matches!(self.inner.kind, Kind::Request)
106 }
107
108 pub fn is_body(&self) -> bool {
110 matches!(self.inner.kind, Kind::Body)
111 }
112
113 pub fn is_decode(&self) -> bool {
115 matches!(self.inner.kind, Kind::Decode)
116 }
117
118 pub fn status(&self) -> Option<StatusCode> {
120 match self.inner.kind {
121 Kind::Status(code) => Some(code),
122 _ => None,
123 }
124 }
125
126 #[allow(unused)]
129 pub(crate) fn into_io(self) -> io::Error {
130 io::Error::other(self)
131 }
132}
133
134impl fmt::Debug for Error {
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 let mut builder = f.debug_struct("golem_wasi_http::Error");
137
138 builder.field("kind", &self.inner.kind);
139
140 if let Some(ref url) = self.inner.url {
141 builder.field("url", &url.as_str());
142 }
143 if let Some(ref source) = self.inner.source {
144 builder.field("source", source);
145 }
146
147 builder.finish()
148 }
149}
150
151impl fmt::Display for Error {
152 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153 match self.inner.kind {
154 Kind::Builder => f.write_str("builder error")?,
155 Kind::Request => f.write_str("error sending request")?,
156 Kind::Body => f.write_str("request or response body error")?,
157 Kind::Decode => f.write_str("error decoding response body")?,
158 Kind::Redirect => f.write_str("error following redirect")?,
159 Kind::Upgrade => f.write_str("error upgrading connection")?,
160 Kind::Status(ref code) => {
161 let prefix = if code.is_client_error() {
162 "HTTP status client error"
163 } else {
164 debug_assert!(code.is_server_error());
165 "HTTP status server error"
166 };
167 write!(f, "{prefix} ({code})")?;
168 }
169 };
170
171 if let Some(url) = &self.inner.url {
172 write!(f, " for url ({url})")?;
173 }
174
175 Ok(())
176 }
177}
178
179impl StdError for Error {
180 fn source(&self) -> Option<&(dyn StdError + 'static)> {
181 self.inner.source.as_ref().map(|e| &**e as _)
182 }
183}
184
185#[derive(Debug)]
186#[allow(dead_code)]
187pub(crate) enum Kind {
188 Builder,
189 Request,
190 Redirect,
191 Status(StatusCode),
192 Body,
193 Decode,
194 Upgrade,
195}
196
197pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
200 Error::new(Kind::Builder, Some(e))
201}
202
203#[allow(dead_code)]
204pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
205 Error::new(Kind::Body, Some(e))
206}
207
208pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
209 Error::new(Kind::Decode, Some(e))
210}
211
212#[allow(dead_code)]
213pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
214 Error::new(Kind::Request, Some(e))
215}
216
217#[allow(dead_code)]
218pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
219 Error::new(Kind::Redirect, Some(e)).with_url(url)
220}
221
222pub(crate) fn status_code(url: Url, status: StatusCode) -> Error {
223 Error::new(Kind::Status(status), None::<Error>).with_url(url)
224}
225
226pub(crate) fn url_bad_scheme(url: Url) -> Error {
227 Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
228}
229
230#[allow(dead_code)]
231pub(crate) fn url_invalid_uri(url: Url) -> Error {
232 Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
233}
234
235#[allow(dead_code)]
236pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
237 Error::new(Kind::Upgrade, Some(e))
238}
239
240#[allow(unused)]
243pub(crate) fn decode_io(e: io::Error) -> Error {
244 if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
245 *e.into_inner()
246 .expect("io::Error::get_ref was Some(_)")
247 .downcast::<Error>()
248 .expect("StdError::is() was true")
249 } else {
250 decode(e)
251 }
252}
253
254#[derive(Debug)]
257pub(crate) struct TimedOut;
258
259impl fmt::Display for TimedOut {
260 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
261 f.write_str("operation timed out")
262 }
263}
264
265impl StdError for TimedOut {}
266
267#[derive(Debug)]
268pub(crate) struct BadScheme;
269
270impl fmt::Display for BadScheme {
271 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
272 f.write_str("URL scheme is not allowed")
273 }
274}
275
276impl StdError for BadScheme {}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 fn assert_send<T: Send>() {}
283 fn assert_sync<T: Sync>() {}
284
285 #[test]
286 fn test_source_chain() {
287 let root = Error::new(Kind::Request, None::<Error>);
288 assert!(root.source().is_none());
289
290 let link = super::body(root);
291 assert!(link.source().is_some());
292 assert_send::<Error>();
293 assert_sync::<Error>();
294 }
295
296 #[test]
297 fn mem_size_of() {
298 use std::mem::size_of;
299 assert_eq!(size_of::<Error>(), size_of::<usize>());
300 }
301
302 #[test]
303 fn roundtrip_io_error() {
304 let orig = super::request("orig");
305 let io = orig.into_io();
307 let err = super::decode_io(io);
309 match err.inner.kind {
311 Kind::Request => (),
312 _ => panic!("{err:?}"),
313 }
314 }
315
316 #[test]
317 fn from_unknown_io_error() {
318 let orig = io::Error::other("orly");
319 let err = super::decode_io(orig);
320 match err.inner.kind {
321 Kind::Decode => (),
322 _ => panic!("{err:?}"),
323 }
324 }
325
326 #[test]
327 fn is_timeout() {
328 let err = super::request(super::TimedOut);
329 assert!(err.is_timeout());
330
331 let io = io::Error::other(err);
332 let nested = super::request(io);
333 assert!(nested.is_timeout());
334 }
335}