1use std::{
2 convert::From,
3 error::Error,
4 fmt,
5 io::Error as IoError,
6 string::FromUtf8Error,
7 time::{Duration, SystemTime},
8};
9
10use http::uri::InvalidUri;
11use serde_json::error::Error as JsonError;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ErrorInfo {
15 pub code: u16,
16 pub errno: u16,
17 pub error: String,
18 pub message: String,
19}
20
21impl fmt::Display for ErrorInfo {
22 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23 write!(
24 f,
25 "code {}, errno {}: {} ({})",
26 self.code, self.errno, self.error, self.message
27 )
28 }
29}
30
31#[derive(Debug)]
32pub enum WebPushError {
33 Unspecified,
35 Unauthorized(ErrorInfo),
37 BadRequest(ErrorInfo),
39 ServerError {
41 retry_after: Option<Duration>,
42 info: ErrorInfo,
43 },
44 NotImplemented(ErrorInfo),
46 InvalidUri,
48 EndpointNotValid(ErrorInfo),
50 EndpointNotFound(ErrorInfo),
52 PayloadTooLarge,
54 Io(IoError),
56 InvalidPackageName,
59 InvalidTtl,
61 InvalidTopic,
63 MissingCryptoKeys,
65 InvalidCryptoKeys,
67 InvalidResponse,
69 InvalidClaims,
71 ResponseTooLarge,
73 Other(ErrorInfo),
74}
75
76impl Error for WebPushError {}
77
78impl From<JsonError> for WebPushError {
79 fn from(_: JsonError) -> WebPushError {
80 WebPushError::InvalidResponse
81 }
82}
83
84impl From<FromUtf8Error> for WebPushError {
85 fn from(_: FromUtf8Error) -> WebPushError {
86 WebPushError::InvalidResponse
87 }
88}
89
90impl From<InvalidUri> for WebPushError {
91 fn from(_: InvalidUri) -> WebPushError {
92 WebPushError::InvalidUri
93 }
94}
95
96#[cfg(feature = "hyper-client")]
97impl From<hyper::Error> for WebPushError {
98 fn from(_: hyper::Error) -> Self {
99 Self::Unspecified
100 }
101}
102
103#[cfg(feature = "isahc-client")]
104impl From<isahc::Error> for WebPushError {
105 fn from(_: isahc::Error) -> Self {
106 Self::Unspecified
107 }
108}
109
110impl From<IoError> for WebPushError {
111 fn from(err: IoError) -> WebPushError {
112 WebPushError::Io(err)
113 }
114}
115
116impl WebPushError {
117 pub fn short_description(&self) -> &'static str {
118 match *self {
119 WebPushError::Unspecified => "unspecified",
120 WebPushError::Unauthorized(_) => "unauthorized",
121 WebPushError::BadRequest(_) => "bad_request",
122 WebPushError::ServerError { .. } => "server_error",
123 WebPushError::NotImplemented(_) => "not_implemented",
124 WebPushError::InvalidUri => "invalid_uri",
125 WebPushError::EndpointNotValid(_) => "endpoint_not_valid",
126 WebPushError::EndpointNotFound(_) => "endpoint_not_found",
127 WebPushError::PayloadTooLarge => "payload_too_large",
128 WebPushError::InvalidPackageName => "invalid_package_name",
129 WebPushError::InvalidTtl => "invalid_ttl",
130 WebPushError::InvalidTopic => "invalid_topic",
131 WebPushError::InvalidResponse => "invalid_response",
132 WebPushError::MissingCryptoKeys => "missing_crypto_keys",
133 WebPushError::InvalidCryptoKeys => "invalid_crypto_keys",
134 WebPushError::Io(_) => "io_error",
135 WebPushError::Other(_) => "other",
136 WebPushError::InvalidClaims => "invalidClaims",
137 WebPushError::ResponseTooLarge => "response_too_large",
138 }
139 }
140}
141
142impl fmt::Display for WebPushError {
143 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144 match self {
145 WebPushError::Unspecified => write!(f, "unspecified error"),
146 WebPushError::Unauthorized(info) => write!(f, "unauthorized: {}", info),
147 WebPushError::BadRequest(info) => write!(f, "bad request: {}", info),
148 WebPushError::ServerError { info, .. } => write!(f, "server error: {}", info),
149 WebPushError::PayloadTooLarge => write!(f, "maximum payload size of 3070 characters exceeded"),
150 WebPushError::InvalidUri => write!(f, "invalid uri provided"),
151 WebPushError::NotImplemented(info) => write!(f, "not implemented: {}", info),
152 WebPushError::EndpointNotValid(info) => write!(f, "endpoint not valid: {}", info),
153 WebPushError::EndpointNotFound(info) => write!(f, "endpoint not found: {}", info),
154 WebPushError::Io(err) => write!(f, "i/o error: {}", err),
155 WebPushError::InvalidPackageName => write!(
156 f,
157 "package name of registration token does not match package name provided in the request"
158 ),
159 WebPushError::InvalidTtl => write!(f, "invalid or missing ttl value"),
160 WebPushError::InvalidTopic => write!(f, "invalid topic value"),
161 WebPushError::InvalidResponse => write!(f, "could not parse response data"),
162 WebPushError::MissingCryptoKeys => write!(f, "request is missing cryptographic keys"),
163 WebPushError::InvalidCryptoKeys => write!(f, "request has invalid cryptographic keys"),
164 WebPushError::Other(info) => write!(f, "other: {}", info),
165 WebPushError::InvalidClaims => write!(f, "at least one jwt claim was invalid"),
166 WebPushError::ResponseTooLarge => write!(f, "response from push endpoint was too large"),
167 }
168 }
169}
170
171pub struct RetryAfter;
172impl RetryAfter {
173 pub fn from_str(header_value: &str) -> Option<Duration> {
174 if let Ok(seconds) = header_value.parse::<u64>() {
175 Some(Duration::from_secs(seconds))
176 } else {
177 chrono::DateTime::parse_from_rfc2822(header_value)
178 .map(|date_time| {
179 let systime: SystemTime = date_time.into();
180
181 systime
182 .duration_since(SystemTime::now())
183 .unwrap_or_else(|_| Duration::new(0, 0))
184 })
185 .ok()
186 }
187 }
188}