gotify/error.rs
1//! [`GotifyError`] and the crate's [`Result`] alias.
2
3use std::time::Duration;
4
5use thiserror::Error;
6
7/// Result alias for this crate's fallible operations.
8pub type Result<T> = std::result::Result<T, GotifyError>;
9
10/// Everything that can go wrong talking to a Gotify server.
11///
12/// Marked `#[non_exhaustive]`: this crate is meant to be published and
13/// consumed externally, so a caller that matches on this must include a
14/// wildcard arm — adding a new variant must never be a semver break for
15/// downstream code.
16#[derive(Debug, Error)]
17#[non_exhaustive]
18pub enum GotifyError {
19 /// [`crate::GotifyConfig::url`] was empty at [`crate::GotifyClient::new`].
20 #[error("GOTIFY_URL is not configured")]
21 MissingUrl,
22
23 /// The requested operation needs a client token
24 /// ([`crate::GotifyConfig::client_token`]), but none is configured.
25 /// Client tokens authenticate management operations (messages,
26 /// applications, clients, current user) — create one under **Clients**
27 /// in the Gotify web UI.
28 #[error(
29 "GOTIFY_CLIENT_TOKEN is required for this operation \
30 (create one under Clients in the Gotify web UI)"
31 )]
32 MissingClientToken,
33
34 /// [`crate::GotifyClient::send_message`] needs an app token
35 /// ([`crate::GotifyConfig::app_token`]), but none is configured. App
36 /// tokens are distinct from client tokens — create one under
37 /// **Applications** in the Gotify web UI.
38 #[error(
39 "GOTIFY_APP_TOKEN is required to send a message \
40 (create one under Applications in the Gotify web UI)"
41 )]
42 MissingAppToken,
43
44 /// The underlying `reqwest::Client` failed to construct.
45 #[error("failed to build the HTTP client: {0}")]
46 ClientBuild(#[source] reqwest::Error),
47
48 /// HTTP 401 — the configured token was rejected.
49 #[error("Gotify rejected the request as unauthorized (HTTP 401): {0}")]
50 Unauthorized(String),
51
52 /// HTTP 404.
53 #[error("Gotify endpoint not found for {method} {url} (HTTP 404)")]
54 NotFound {
55 /// HTTP method of the request.
56 method: String,
57 /// Full request URL.
58 url: String,
59 },
60
61 /// HTTP 429.
62 #[error(
63 "Gotify rate limit exceeded for {method} {url} (HTTP 429){}",
64 retry_after.map(|d| format!(" - retry after {}s", d.as_secs())).unwrap_or_default()
65 )]
66 RateLimited {
67 /// HTTP method of the rate-limited request.
68 method: String,
69 /// Full request URL.
70 url: String,
71 /// Parsed `Retry-After` response header, when present and expressed
72 /// in seconds (the HTTP-date form is not parsed).
73 retry_after: Option<Duration>,
74 },
75
76 /// Any other non-success status. `body` is JSON when the response was
77 /// JSON, otherwise the raw text; boxed to keep this enum small.
78 #[error("Gotify HTTP {status} for {url}: {body}")]
79 UnexpectedStatus {
80 /// HTTP status code.
81 status: u16,
82 /// Full request URL.
83 url: String,
84 /// Best-effort response body.
85 body: Box<serde_json::Value>,
86 },
87
88 /// The response body wasn't valid JSON on an otherwise-successful status.
89 #[error("failed to decode Gotify response from {url}: {source}")]
90 Decode {
91 /// Full request URL.
92 url: String,
93 /// The underlying deserialization failure.
94 #[source]
95 source: serde_json::Error,
96 },
97
98 /// Request timed out.
99 #[error("Gotify request timed out: {method} {url}")]
100 Timeout {
101 /// HTTP method of the request.
102 method: String,
103 /// Full request URL.
104 url: String,
105 /// The underlying transport error.
106 #[source]
107 source: reqwest::Error,
108 },
109
110 /// Failed to connect to the configured URL.
111 #[error("failed to connect to Gotify at {url} ({method}) — check GOTIFY_URL is reachable")]
112 Connect {
113 /// HTTP method of the request.
114 method: String,
115 /// Full request URL.
116 url: String,
117 /// The underlying transport error.
118 #[source]
119 source: reqwest::Error,
120 },
121
122 /// Any other transport-level failure.
123 #[error("Gotify request failed: {method} {url}: {source}")]
124 Request {
125 /// HTTP method of the request.
126 method: String,
127 /// Full request URL.
128 url: String,
129 /// The underlying transport error.
130 #[source]
131 source: reqwest::Error,
132 },
133}