Skip to main content

ploidy_util/
error.rs

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/// A client error.
9#[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    /// Creates an error for an invalid HTTP header name.
26    #[cold]
27    pub fn bad_header_name(err: impl Into<http::Error>) -> Self {
28        Self::Build(BuildError::HeaderName(err.into()))
29    }
30
31    /// Creates an error for an invalid HTTP header value.
32    #[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    /// Returns the telemetry category for this error.
38    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/// Invalid or unexpected response body, with or without a path
112/// to the unexpected section.
113#[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/// The telemetry category for an [`Error`].
122#[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}