oauth2_broker/
error.rs

1//! Broker-level error types shared across flows, providers, and stores.
2
3// self
4use crate::_prelude::*;
5
6/// Broker-wide result type alias returning [`Error`] by default.
7pub type Result<T, E = Error> = std::result::Result<T, E>;
8
9type BoxError = Box<dyn std::error::Error + Send + Sync>;
10
11/// Canonical broker error exposed by public APIs.
12#[derive(Debug, ThisError)]
13pub enum Error {
14	/// Storage-layer failure.
15	#[error("{0}")]
16	Storage(
17		#[from]
18		#[source]
19		crate::store::StoreError,
20	),
21	/// Local configuration problem.
22	#[error(transparent)]
23	Config(#[from] ConfigError),
24	/// Temporary upstream failure; retry with backoff.
25	#[error(transparent)]
26	Transient(#[from] TransientError),
27	/// Transport failure (DNS, TCP, TLS).
28	#[error(transparent)]
29	Transport(#[from] TransportError),
30
31	/// Requested scopes exceed what was granted.
32	#[error("Token lacks the required scopes: {reason}.")]
33	InsufficientScope {
34		/// Provider- or broker-supplied reason string.
35		reason: String,
36	},
37	/// Provider rejected the grant (e.g., bad code or refresh token).
38	#[error("Provider rejected the grant: {reason}.")]
39	InvalidGrant {
40		/// Provider- or broker-supplied reason string.
41		reason: String,
42	},
43	/// Client authentication failed or credentials are malformed.
44	#[error("Client authentication failed: {reason}.")]
45	InvalidClient {
46		/// Provider- or broker-supplied reason string.
47		reason: String,
48	},
49	/// Token has been revoked and must not be reused.
50	#[error("Token has been revoked.")]
51	Revoked,
52}
53
54/// Configuration and validation failures raised by the broker.
55#[derive(Debug, ThisError)]
56pub enum ConfigError {
57	/// HTTP client could not be constructed.
58	#[error("HTTP client could not be constructed.")]
59	HttpClientBuild {
60		/// Underlying transport builder failure.
61		#[source]
62		source: BoxError,
63	},
64	/// HTTP request construction failed.
65	#[error(transparent)]
66	HttpRequest(#[from] oauth2::http::Error),
67	/// Provider descriptor contains an invalid URL.
68	#[error("Descriptor contains an invalid URL.")]
69	InvalidDescriptor {
70		/// Underlying parsing failure.
71		#[source]
72		source: oauth2::url::ParseError,
73	},
74	/// Redirect URI cannot be parsed.
75	#[error("Redirect URI is invalid.")]
76	InvalidRedirect {
77		/// Underlying parsing failure.
78		#[source]
79		source: oauth2::url::ParseError,
80	},
81
82	/// Descriptor does not enable the requested grant.
83	#[error("Descriptor `{descriptor}` does not enable the {grant} grant.")]
84	UnsupportedGrant {
85		/// Provider identifier string.
86		descriptor: String,
87		/// Disabled grant label.
88		grant: &'static str,
89	},
90	/// Cached record is missing a refresh secret.
91	#[error("Cached token record is missing a refresh token.")]
92	MissingRefreshToken,
93	/// Request scopes cannot be normalized.
94	#[error("Requested scopes are invalid.")]
95	InvalidScope(#[from] crate::auth::ScopeValidationError),
96	/// Token record builder validation failed.
97	#[error("Unable to build token record.")]
98	TokenBuild(#[from] crate::auth::TokenRecordBuilderError),
99	/// Token endpoint response omitted `expires_in`.
100	#[error("Token endpoint response is missing expires_in.")]
101	MissingExpiresIn,
102	/// Token endpoint returned an excessively large `expires_in`.
103	#[error("The expires_in value exceeds the supported range.")]
104	ExpiresInOutOfRange,
105	/// Token endpoint returned a non-positive duration.
106	#[error("The expires_in value must be positive.")]
107	NonPositiveExpiresIn,
108	/// Provider changed scopes during the exchange.
109	#[error("Token endpoint changed scopes during the {grant} grant.")]
110	ScopesChanged {
111		/// Grant label.
112		grant: &'static str,
113	},
114}
115impl ConfigError {
116	/// Wraps a transport's builder failure inside [`ConfigError`].
117	pub fn http_client_build(src: impl 'static + Send + Sync + std::error::Error) -> Self {
118		Self::HttpClientBuild { source: Box::new(src) }
119	}
120}
121#[cfg(feature = "reqwest")]
122impl From<reqwest::Error> for ConfigError {
123	fn from(e: reqwest::Error) -> Self {
124		Self::http_client_build(e)
125	}
126}
127
128/// Temporary failure variants (safe to retry).
129#[derive(Debug, ThisError)]
130pub enum TransientError {
131	/// Provider returned an unexpected but non-fatal response.
132	#[error("Token endpoint returned an unexpected response: {message}.")]
133	TokenEndpoint {
134		/// Provider- or broker-supplied message summarizing the failure.
135		message: String,
136		/// HTTP status code, when available.
137		status: Option<u16>,
138		/// Retry-After hint from upstream, if supplied.
139		retry_after: Option<Duration>,
140	},
141	/// Token endpoint responded with malformed JSON that could not be parsed.
142	#[error("Token endpoint returned malformed JSON.")]
143	TokenResponseParse {
144		/// Structured parsing failure.
145		#[source]
146		source: serde_path_to_error::Error<serde_json::error::Error>,
147		/// HTTP status code, when available.
148		status: Option<u16>,
149	},
150}
151/// Transport-level failures (network, IO).
152#[derive(Debug, ThisError)]
153pub enum TransportError {
154	/// Underlying HTTP client reported a network failure.
155	#[error("Network error occurred while calling the token endpoint.")]
156	Network {
157		/// Transport-specific network error.
158		#[source]
159		source: BoxError,
160	},
161	/// Underlying IO failure surfaced during transport.
162	#[error("I/O error occurred while calling the token endpoint.")]
163	Io(#[from] std::io::Error),
164}
165impl TransportError {
166	/// Wraps a transport-specific network error.
167	pub fn network(src: impl 'static + Send + Sync + std::error::Error) -> Self {
168		Self::Network { source: Box::new(src) }
169	}
170}
171#[cfg(feature = "reqwest")]
172impl From<ReqwestError> for TransportError {
173	fn from(e: ReqwestError) -> Self {
174		Self::network(e)
175	}
176}