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}
121impl From<reqwest::Error> for ConfigError {
122	fn from(e: reqwest::Error) -> Self {
123		Self::http_client_build(e)
124	}
125}
126
127/// Temporary failure variants (safe to retry).
128#[derive(Debug, ThisError)]
129pub enum TransientError {
130	/// Provider returned an unexpected but non-fatal response.
131	#[error("Token endpoint returned an unexpected response: {message}.")]
132	TokenEndpoint {
133		/// Provider- or broker-supplied message summarizing the failure.
134		message: String,
135		/// HTTP status code, when available.
136		status: Option<u16>,
137		/// Retry-After hint from upstream, if supplied.
138		retry_after: Option<Duration>,
139	},
140	/// Token endpoint responded with malformed JSON that could not be parsed.
141	#[error("Token endpoint returned malformed JSON.")]
142	TokenResponseParse {
143		/// Structured parsing failure.
144		#[source]
145		source: serde_path_to_error::Error<serde_json::error::Error>,
146		/// HTTP status code, when available.
147		status: Option<u16>,
148	},
149}
150/// Transport-level failures (network, IO).
151#[derive(Debug, ThisError)]
152pub enum TransportError {
153	/// Underlying HTTP client reported a network failure.
154	#[error("Network error occurred while calling the token endpoint.")]
155	Network {
156		/// Transport-specific network error.
157		#[source]
158		source: BoxError,
159	},
160	/// Underlying IO failure surfaced during transport.
161	#[error("I/O error occurred while calling the token endpoint.")]
162	Io(#[from] std::io::Error),
163}
164impl TransportError {
165	/// Wraps a transport-specific network error.
166	pub fn network(src: impl 'static + Send + Sync + std::error::Error) -> Self {
167		Self::Network { source: Box::new(src) }
168	}
169}
170impl From<ReqwestError> for TransportError {
171	fn from(e: ReqwestError) -> Self {
172		Self::network(e)
173	}
174}