1use std::fmt::{Display, Formatter, Result as FmtResult};
2
3use http::{HeaderName, StatusCode};
4use url::ParseError as UrlParseError;
5
6use crate::{query::QueryParamError, url::PathAndQueryError};
7
8#[derive(Debug, thiserror::Error)]
10pub enum Error {
11 #[error("error building request")]
12 Build(#[from] BuildError),
13
14 #[error("HTTP transport error")]
15 Transport(#[source] reqwest::Error),
16
17 #[error("HTTP status error ({0})")]
18 Status(StatusCode),
19
20 #[error("invalid or unexpected response body")]
21 Body(#[from] BodyError),
22}
23
24impl Error {
25 #[cold]
27 pub fn bad_header_name(err: impl Into<http::Error>) -> Self {
28 Self::Build(BuildError::HeaderName(err.into()))
29 }
30
31 #[cold]
33 pub fn bad_header_value(name: HeaderName, err: impl Into<http::Error>) -> Self {
34 Self::Build(BuildError::HeaderValue(name, err.into()))
35 }
36
37 pub fn category(&self) -> ErrorCategory {
39 match self {
40 Self::Build(_) => ErrorCategory::Build,
41 Self::Transport(err) if err.is_timeout() => ErrorCategory::Timeout,
42 Self::Transport(err) if err.is_connect() => ErrorCategory::Connect,
43 Self::Transport(_) => ErrorCategory::Transport,
44 &Self::Status(status) => ErrorCategory::Status(status),
45 Self::Body(_) => ErrorCategory::Body,
46 }
47 }
48}
49
50impl From<QueryParamError> for Error {
51 fn from(err: QueryParamError) -> Self {
52 Self::Build(BuildError::QueryParam(err))
53 }
54}
55
56impl From<PathAndQueryError> for Error {
57 fn from(err: PathAndQueryError) -> Self {
58 Self::Build(BuildError::Path(err))
59 }
60}
61
62impl From<UrlParseError> for Error {
63 fn from(err: UrlParseError) -> Self {
64 Self::Build(BuildError::Url(err))
65 }
66}
67
68impl From<reqwest::Error> for Error {
69 #[cold]
70 fn from(err: reqwest::Error) -> Self {
71 if err.is_builder() {
72 Self::Build(BuildError::Request(err))
73 } else if let Some(status) = err.status() {
74 Self::Status(status)
75 } else {
76 Self::Transport(err)
77 }
78 }
79}
80
81impl From<serde_json::Error> for Error {
82 #[cold]
83 fn from(err: serde_json::Error) -> Self {
84 Self::Body(BodyError::Json(err))
85 }
86}
87
88impl From<serde_path_to_error::Error<serde_json::Error>> for Error {
89 #[cold]
90 fn from(err: serde_path_to_error::Error<serde_json::Error>) -> Self {
91 Self::Body(BodyError::JsonWithPath(err))
92 }
93}
94
95#[derive(Debug, thiserror::Error)]
96pub enum BuildError {
97 #[error("invalid URL")]
98 Url(#[source] UrlParseError),
99 #[error("invalid query parameter")]
100 QueryParam(#[source] QueryParamError),
101 #[error(transparent)]
102 Path(PathAndQueryError),
103 #[error("invalid header name")]
104 HeaderName(#[source] http::Error),
105 #[error("invalid value for header `{0}`")]
106 HeaderValue(HeaderName, #[source] http::Error),
107 #[error(transparent)]
108 Request(reqwest::Error),
109}
110
111#[derive(Debug, thiserror::Error)]
114pub enum BodyError {
115 #[error(transparent)]
116 Json(serde_json::Error),
117 #[error(transparent)]
118 JsonWithPath(serde_path_to_error::Error<serde_json::Error>),
119}
120
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
123pub enum ErrorCategory {
124 Build,
125 Connect,
126 Timeout,
127 Transport,
128 Status(StatusCode),
129 Body,
130}
131
132impl Display for ErrorCategory {
133 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
134 f.write_str(match self {
135 Self::Build => "build",
136 Self::Connect => "connect",
137 Self::Timeout => "timeout",
138 Self::Transport => "transport",
139 Self::Status(status) => status.as_str(),
140 Self::Body => "body",
141 })
142 }
143}