web_push/
error.rs

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    /// An unknown error happened while encrypting or sending the message
34    Unspecified,
35    /// Please provide valid credentials to send the notification
36    Unauthorized(ErrorInfo),
37    /// Request was badly formed
38    BadRequest(ErrorInfo),
39    /// Contains an optional `Duration`, until the user can retry the request
40    ServerError {
41        retry_after: Option<Duration>,
42        info: ErrorInfo,
43    },
44    /// The feature is not implemented yet
45    NotImplemented(ErrorInfo),
46    /// The provided URI is invalid
47    InvalidUri,
48    /// The URL specified is no longer valid and should no longer be used
49    EndpointNotValid(ErrorInfo),
50    /// The URL specified is invalid and should not be used again
51    EndpointNotFound(ErrorInfo),
52    /// Maximum allowed payload size is 3800 characters
53    PayloadTooLarge,
54    /// Error in reading a file
55    Io(IoError),
56    /// Make sure the message was addressed to a registration token whose
57    /// package name matches the value passed in the request (Google).
58    InvalidPackageName,
59    /// The TTL value provided was not valid or was not provided
60    InvalidTtl,
61    /// The Topic value provided was invalid
62    InvalidTopic,
63    /// The request was missing required crypto keys
64    MissingCryptoKeys,
65    /// One or more of the crypto key elements are invalid.
66    InvalidCryptoKeys,
67    /// Corrupted response data
68    InvalidResponse,
69    /// A claim had invalid data
70    InvalidClaims,
71    /// Response from push endpoint was too large
72    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}