oauth2/lib.rs
1#![warn(missing_docs)]
2//!
3//! An extensible, strongly-typed implementation of OAuth2
4//! ([RFC 6749](https://tools.ietf.org/html/rfc6749)) including token introspection ([RFC 7662](https://tools.ietf.org/html/rfc7662))
5//! and token revocation ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
6//!
7//! # Contents
8//! * [Importing `oauth2`: selecting an HTTP client interface](#importing-oauth2-selecting-an-http-client-interface)
9//! * [Getting started: Authorization Code Grant w/ PKCE](#getting-started-authorization-code-grant-w-pkce)
10//! * [Example: Synchronous (blocking) API](#example-synchronous-blocking-api)
11//! * [Example: Asynchronous API](#example-asynchronous-api)
12//! * [Implicit Grant](#implicit-grant)
13//! * [Resource Owner Password Credentials Grant](#resource-owner-password-credentials-grant)
14//! * [Client Credentials Grant](#client-credentials-grant)
15//! * [Device Authorization Flow](#device-authorization-flow)
16//! * [Other examples](#other-examples)
17//! * [Contributed Examples](#contributed-examples)
18//!
19//! # Importing `oauth2`: selecting an HTTP client interface
20//!
21//! This library offers a flexible HTTP client interface with two modes:
22//! * **Synchronous (blocking)**
23//!
24//! NOTE: Be careful not to use a blocking HTTP client within `async` Rust code, which may panic
25//! or cause other issues. The
26//! [`tokio::task::spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)
27//! function may be useful in this situation.
28//! * **Asynchronous**
29//!
30//! ## Security Warning
31//!
32//! To prevent
33//! [SSRF](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)
34//! vulnerabilities, be sure to configure the HTTP client **not to follow redirects**. For example,
35//! use [`redirect::Policy::none`](reqwest::redirect::Policy::none) when using
36//! [`reqwest`], or [`redirects(0)`](ureq::AgentBuilder::redirects) when using [`ureq`].
37//!
38//! ## HTTP Clients
39//!
40//! For the HTTP client modes described above, the following HTTP client implementations can be
41//! used:
42//! * **[`reqwest`](reqwest)**
43//!
44//! The `reqwest` HTTP client supports both the synchronous and asynchronous modes and is enabled
45//! by default.
46//!
47//! Synchronous client: [`reqwest::blocking::Client`] (requires the
48//! `reqwest-blocking` feature flag)
49//!
50//! Asynchronous client: [`reqwest::Client`] (requires either the
51//! `reqwest` or `reqwest-blocking` feature flags)
52//!
53//! * **[`curl`](curl)**
54//!
55//! The `curl` HTTP client only supports the synchronous HTTP client mode and can be enabled in
56//! `Cargo.toml` via the `curl` feature flag.
57//!
58//! Synchronous client: [`oauth2::CurlHttpClient`](CurlHttpClient)
59//!
60//! * **[`ureq`](ureq)**
61//!
62//! The `ureq` HTTP client is a simple HTTP client with minimal dependencies. It only supports
63//! the synchronous HTTP client mode and can be enabled in `Cargo.toml` via the `ureq` feature
64//! flag.
65//!
66//! Synchronous client: [`ureq::Agent`]
67//!
68//! * **Custom**
69//!
70//! In addition to the clients above, users may define their own HTTP clients, which must accept
71//! an [`HttpRequest`] and return an [`HttpResponse`] or error. Users writing their own clients
72//! may wish to disable the default `reqwest` dependency by specifying
73//! `default-features = false` in `Cargo.toml` (replacing `...` with the desired version of this
74//! crate):
75//! ```toml
76//! oauth2 = { version = "...", default-features = false }
77//! ```
78//!
79//! Synchronous HTTP clients should implement the [`SyncHttpClient`] trait, which is
80//! automatically implemented for any function/closure that implements:
81//! ```rust,ignore
82//! Fn(HttpRequest) -> Result<HttpResponse, E>
83//! where
84//! E: std::error::Error + 'static
85//! ```
86//!
87//! Asynchronous HTTP clients should implement the [`AsyncHttpClient`] trait, which is
88//! automatically implemented for any function/closure that implements:
89//! ```rust,ignore
90//! Fn(HttpRequest) -> F
91//! where
92//! E: std::error::Error + 'static,
93//! F: Future<Output = Result<HttpResponse, E>>,
94//! ```
95//!
96//! # Comparing secrets securely
97//!
98//! OAuth flows require comparing secrets received from the provider servers. To do so securely
99//! while avoiding [timing side-channels](https://en.wikipedia.org/wiki/Timing_attack), the
100//! comparison must be done in constant time, either using a constant-time crate such as
101//! [`constant_time_eq`](https://crates.io/crates/constant_time_eq) (which could break if a future
102//! compiler version decides to be overly smart
103//! about its optimizations), or by first computing a cryptographically-secure hash (e.g., SHA-256)
104//! of both values and then comparing the hashes using `==`.
105//!
106//! The `timing-resistant-secret-traits` feature flag adds a safe (but comparatively expensive)
107//! [`PartialEq`] implementation to the secret types. Timing side-channels are why [`PartialEq`] is
108//! not auto-derived for this crate's secret types, and the lack of [`PartialEq`] is intended to
109//! prompt users to think more carefully about these comparisons.
110//!
111//! # Getting started: Authorization Code Grant w/ PKCE
112//!
113//! This is the most common OAuth2 flow. PKCE is recommended whenever the OAuth2 client has no
114//! client secret or has a client secret that cannot remain confidential (e.g., native, mobile, or
115//! client-side web applications).
116//!
117//! ## Example: Synchronous (blocking) API
118//!
119//! This example works with `oauth2`'s default feature flags, which include `reqwest`.
120//!
121//! ```rust,no_run
122//! use oauth2::{
123//! AuthorizationCode,
124//! AuthUrl,
125//! ClientId,
126//! ClientSecret,
127//! CsrfToken,
128//! PkceCodeChallenge,
129//! RedirectUrl,
130//! Scope,
131//! TokenResponse,
132//! TokenUrl
133//! };
134//! use oauth2::basic::BasicClient;
135//! # #[cfg(feature = "reqwest-blocking")]
136//! use oauth2::reqwest;
137//! use url::Url;
138//!
139//! # #[cfg(feature = "reqwest-blocking")]
140//! # fn err_wrapper() -> Result<(), anyhow::Error> {
141//! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
142//! // token URL.
143//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
144//! .set_client_secret(ClientSecret::new("client_secret".to_string()))
145//! .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
146//! .set_token_uri(TokenUrl::new("http://token".to_string())?)
147//! // Set the URL the user will be redirected to after the authorization process.
148//! .set_redirect_uri(RedirectUrl::new("http://redirect".to_string())?);
149//!
150//! // Generate a PKCE challenge.
151//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
152//!
153//! // Generate the full authorization URL.
154//! let (auth_url, csrf_token) = client
155//! .authorize_url(CsrfToken::new_random)
156//! // Set the desired scopes.
157//! .add_scope(Scope::new("read".to_string()))
158//! .add_scope(Scope::new("write".to_string()))
159//! // Set the PKCE code challenge.
160//! .set_pkce_challenge(pkce_challenge)
161//! .url();
162//!
163//! // This is the URL you should redirect the user to, in order to trigger the authorization
164//! // process.
165//! println!("Browse to: {}", auth_url);
166//!
167//! // Once the user has been redirected to the redirect URL, you'll have access to the
168//! // authorization code. For security reasons, your code should verify that the `state`
169//! // parameter returned by the server matches `csrf_token`.
170//!
171//! let http_client = reqwest::blocking::ClientBuilder::new()
172//! // Following redirects opens the client up to SSRF vulnerabilities.
173//! .redirect(reqwest::redirect::Policy::none())
174//! .build()
175//! .expect("Client should build");
176//!
177//! // Now you can trade it for an access token.
178//! let token_result =
179//! client
180//! .exchange_code(AuthorizationCode::new("some authorization code".to_string()))
181//! // Set the PKCE code verifier.
182//! .set_pkce_verifier(pkce_verifier)
183//! .request(&http_client)?;
184//!
185//! // Unwrapping token_result will either produce a Token or a RequestTokenError.
186//! # Ok(())
187//! # }
188//! ```
189//!
190//! ## Example: Asynchronous API
191//!
192//! The example below uses async/await:
193//!
194//! ```rust,no_run
195//! use oauth2::{
196//! AuthorizationCode,
197//! AuthUrl,
198//! ClientId,
199//! ClientSecret,
200//! CsrfToken,
201//! PkceCodeChallenge,
202//! RedirectUrl,
203//! Scope,
204//! TokenResponse,
205//! TokenUrl
206//! };
207//! use oauth2::basic::BasicClient;
208//! # #[cfg(feature = "reqwest")]
209//! use oauth2::reqwest;
210//! use url::Url;
211//!
212//! # #[cfg(feature = "reqwest")]
213//! # async fn err_wrapper() -> Result<(), anyhow::Error> {
214//! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
215//! // token URL.
216//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
217//! .set_client_secret(ClientSecret::new("client_secret".to_string()))
218//! .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
219//! .set_token_uri(TokenUrl::new("http://token".to_string())?)
220//! // Set the URL the user will be redirected to after the authorization process.
221//! .set_redirect_uri(RedirectUrl::new("http://redirect".to_string())?);
222//!
223//! // Generate a PKCE challenge.
224//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
225//!
226//! // Generate the full authorization URL.
227//! let (auth_url, csrf_token) = client
228//! .authorize_url(CsrfToken::new_random)
229//! // Set the desired scopes.
230//! .add_scope(Scope::new("read".to_string()))
231//! .add_scope(Scope::new("write".to_string()))
232//! // Set the PKCE code challenge.
233//! .set_pkce_challenge(pkce_challenge)
234//! .url();
235//!
236//! // This is the URL you should redirect the user to, in order to trigger the authorization
237//! // process.
238//! println!("Browse to: {}", auth_url);
239//!
240//! // Once the user has been redirected to the redirect URL, you'll have access to the
241//! // authorization code. For security reasons, your code should verify that the `state`
242//! // parameter returned by the server matches `csrf_token`.
243//!
244//! let http_client = reqwest::ClientBuilder::new()
245//! // Following redirects opens the client up to SSRF vulnerabilities.
246//! .redirect(reqwest::redirect::Policy::none())
247//! .build()
248//! .expect("Client should build");
249//!
250//! // Now you can trade it for an access token.
251//! let token_result = client
252//! .exchange_code(AuthorizationCode::new("some authorization code".to_string()))
253//! // Set the PKCE code verifier.
254//! .set_pkce_verifier(pkce_verifier)
255//! .request_async(&http_client)
256//! .await?;
257//!
258//! // Unwrapping token_result will either produce a Token or a RequestTokenError.
259//! # Ok(())
260//! # }
261//! ```
262//!
263//! # Implicit Grant
264//!
265//! This flow fetches an access token directly from the authorization endpoint. Be sure to
266//! understand the security implications of this flow before using it. In most cases, the
267//! Authorization Code Grant flow is preferable to the Implicit Grant flow.
268//!
269//! ## Example
270//!
271//! ```rust,no_run
272//! use oauth2::{
273//! AuthUrl,
274//! ClientId,
275//! CsrfToken,
276//! RedirectUrl,
277//! Scope
278//! };
279//! use oauth2::basic::BasicClient;
280//! use url::Url;
281//!
282//! # fn err_wrapper() -> Result<(), anyhow::Error> {
283//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
284//! .set_auth_uri(AuthUrl::new("http://authorize".to_string())?);
285//!
286//! // Generate the full authorization URL.
287//! let (auth_url, csrf_token) = client
288//! .authorize_url(CsrfToken::new_random)
289//! .use_implicit_flow()
290//! .url();
291//!
292//! // This is the URL you should redirect the user to, in order to trigger the authorization
293//! // process.
294//! println!("Browse to: {}", auth_url);
295//!
296//! // Once the user has been redirected to the redirect URL, you'll have the access code.
297//! // For security reasons, your code should verify that the `state` parameter returned by the
298//! // server matches `csrf_token`.
299//!
300//! # Ok(())
301//! # }
302//! ```
303//!
304//! # Resource Owner Password Credentials Grant
305//!
306//! You can ask for a *password* access token by calling the `Client::exchange_password` method,
307//! while including the username and password.
308//!
309//! ## Example
310//!
311//! ```rust,no_run
312//! use oauth2::{
313//! AuthUrl,
314//! ClientId,
315//! ClientSecret,
316//! ResourceOwnerPassword,
317//! ResourceOwnerUsername,
318//! Scope,
319//! TokenResponse,
320//! TokenUrl
321//! };
322//! use oauth2::basic::BasicClient;
323//! # #[cfg(feature = "reqwest-blocking")]
324//! use oauth2::reqwest;
325//! use url::Url;
326//!
327//! # #[cfg(feature = "reqwest-blocking")]
328//! # fn err_wrapper() -> Result<(), anyhow::Error> {
329//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
330//! .set_client_secret(ClientSecret::new("client_secret".to_string()))
331//! .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
332//! .set_token_uri(TokenUrl::new("http://token".to_string())?);
333//!
334//! let http_client = reqwest::blocking::ClientBuilder::new()
335//! // Following redirects opens the client up to SSRF vulnerabilities.
336//! .redirect(reqwest::redirect::Policy::none())
337//! .build()
338//! .expect("Client should build");
339//!
340//! let token_result =
341//! client
342//! .exchange_password(
343//! &ResourceOwnerUsername::new("user".to_string()),
344//! &ResourceOwnerPassword::new("pass".to_string())
345//! )
346//! .add_scope(Scope::new("read".to_string()))
347//! .request(&http_client)?;
348//! # Ok(())
349//! # }
350//! ```
351//!
352//! # Client Credentials Grant
353//!
354//! You can ask for a *client credentials* access token by calling the
355//! `Client::exchange_client_credentials` method.
356//!
357//! ## Example
358//!
359//! ```rust,no_run
360//! use oauth2::{
361//! AuthUrl,
362//! ClientId,
363//! ClientSecret,
364//! Scope,
365//! TokenResponse,
366//! TokenUrl
367//! };
368//! use oauth2::basic::BasicClient;
369//! # #[cfg(feature = "reqwest-blocking")]
370//! use oauth2::reqwest;
371//! use url::Url;
372//!
373//! # #[cfg(feature = "reqwest-blocking")]
374//! # fn err_wrapper() -> Result<(), anyhow::Error> {
375//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
376//! .set_client_secret(ClientSecret::new("client_secret".to_string()))
377//! .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
378//! .set_token_uri(TokenUrl::new("http://token".to_string())?);
379//!
380//! let http_client = reqwest::blocking::ClientBuilder::new()
381//! // Following redirects opens the client up to SSRF vulnerabilities.
382//! .redirect(reqwest::redirect::Policy::none())
383//! .build()
384//! .expect("Client should build");
385//!
386//! let token_result = client
387//! .exchange_client_credentials()
388//! .add_scope(Scope::new("read".to_string()))
389//! .request(&http_client)?;
390//! # Ok(())
391//! # }
392//! ```
393//!
394//! # Device Authorization Flow
395//!
396//! Device Authorization Flow allows users to sign in on browserless or input-constrained
397//! devices. This is a two-stage process; first a user-code and verification
398//! URL are obtained by using the `Client::exchange_client_credentials`
399//! method. Those are displayed to the user, then are used in a second client
400//! to poll the token endpoint for a token.
401//!
402//! ## Example
403//!
404//! ```rust,no_run
405//! use oauth2::{
406//! AuthUrl,
407//! ClientId,
408//! ClientSecret,
409//! DeviceAuthorizationUrl,
410//! Scope,
411//! StandardDeviceAuthorizationResponse,
412//! TokenResponse,
413//! TokenUrl
414//! };
415//! use oauth2::basic::BasicClient;
416//! # #[cfg(feature = "reqwest-blocking")]
417//! use oauth2::reqwest;
418//! use url::Url;
419//!
420//! # #[cfg(feature = "reqwest-blocking")]
421//! # fn err_wrapper() -> Result<(), anyhow::Error> {
422//! let device_auth_url = DeviceAuthorizationUrl::new("http://deviceauth".to_string())?;
423//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
424//! .set_client_secret(ClientSecret::new("client_secret".to_string()))
425//! .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
426//! .set_token_uri(TokenUrl::new("http://token".to_string())?)
427//! .set_device_authorization_url(device_auth_url);
428//!
429//! let http_client = reqwest::blocking::ClientBuilder::new()
430//! // Following redirects opens the client up to SSRF vulnerabilities.
431//! .redirect(reqwest::redirect::Policy::none())
432//! .build()
433//! .expect("Client should build");
434//!
435//! let details: StandardDeviceAuthorizationResponse = client
436//! .exchange_device_code()
437//! .add_scope(Scope::new("read".to_string()))
438//! .request(&http_client)?;
439//!
440//! println!(
441//! "Open this URL in your browser:\n{}\nand enter the code: {}",
442//! details.verification_uri().to_string(),
443//! details.user_code().secret().to_string()
444//! );
445//!
446//! let token_result =
447//! client
448//! .exchange_device_access_token(&details)
449//! .request(&http_client, std::thread::sleep, None)?;
450//!
451//! # Ok(())
452//! # }
453//! ```
454//!
455//! # Other examples
456//!
457//! More specific implementations are available as part of the examples:
458//!
459//! - [Google](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/google.rs) (includes token revocation)
460//! - [Github](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/github.rs)
461//! - [Microsoft Device Authorization Flow (async)](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/microsoft_devicecode.rs)
462//! - [Microsoft Graph](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/msgraph.rs)
463//! - [Wunderlist](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/wunderlist.rs)
464//!
465//! ## Contributed Examples
466//!
467//! - [`actix-web-oauth2`](https://github.com/pka/actix-web-oauth2) (version 2.x of this crate)
468//!
469
470/// Basic OAuth2 implementation with no extensions
471/// ([RFC 6749](https://tools.ietf.org/html/rfc6749)).
472pub mod basic;
473
474mod client;
475
476mod code;
477
478/// HTTP client backed by the [curl](https://crates.io/crates/curl) crate.
479/// Requires "curl" feature.
480#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
481mod curl_client;
482
483#[cfg(all(feature = "curl", target_arch = "wasm32"))]
484compile_error!("wasm32 is not supported with the `curl` feature. Use the `reqwest` backend or a custom backend for wasm32 support");
485
486/// Device Authorization Flow OAuth2 implementation
487/// ([RFC 8628](https://tools.ietf.org/html/rfc8628)).
488mod devicecode;
489
490mod endpoint;
491
492mod error;
493
494/// Helper methods used by OAuth2 implementations/extensions.
495pub mod helpers;
496
497mod introspection;
498
499/// HTTP client backed by the [reqwest](https://crates.io/crates/reqwest) crate.
500/// Requires "reqwest" feature.
501#[cfg(any(feature = "reqwest", feature = "reqwest-blocking"))]
502mod reqwest_client;
503
504/// OAuth 2.0 Token Revocation implementation
505/// ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
506mod revocation;
507
508#[cfg(test)]
509mod tests;
510
511mod token;
512
513mod types;
514
515/// HTTP client backed by the [ureq](https://crates.io/crates/ureq) crate.
516/// Requires "ureq" feature.
517#[cfg(feature = "ureq")]
518mod ureq_client;
519
520pub use crate::client::{Client, EndpointMaybeSet, EndpointNotSet, EndpointSet, EndpointState};
521pub use crate::code::AuthorizationRequest;
522#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
523pub use crate::curl_client::CurlHttpClient;
524pub use crate::devicecode::{
525 DeviceAccessTokenRequest, DeviceAuthorizationRequest, DeviceAuthorizationResponse,
526 DeviceCodeErrorResponse, DeviceCodeErrorResponseType, EmptyExtraDeviceAuthorizationFields,
527 ExtraDeviceAuthorizationFields, StandardDeviceAuthorizationResponse,
528};
529pub use crate::endpoint::{AsyncHttpClient, HttpRequest, HttpResponse, SyncHttpClient};
530pub use crate::error::{
531 ErrorResponse, ErrorResponseType, RequestTokenError, StandardErrorResponse,
532};
533pub use crate::introspection::{
534 IntrospectionRequest, StandardTokenIntrospectionResponse, TokenIntrospectionResponse,
535};
536pub use crate::revocation::{
537 RevocableToken, RevocationErrorResponseType, RevocationRequest, StandardRevocableToken,
538};
539pub use crate::token::{
540 ClientCredentialsTokenRequest, CodeTokenRequest, EmptyExtraTokenFields, ExtraTokenFields,
541 PasswordTokenRequest, RefreshTokenRequest, StandardTokenResponse, TokenResponse, TokenType,
542};
543pub use crate::types::{
544 AccessToken, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
545 DeviceAuthorizationUrl, DeviceCode, EndUserVerificationUrl, IntrospectionUrl,
546 PkceCodeChallenge, PkceCodeChallengeMethod, PkceCodeVerifier, RedirectUrl, RefreshToken,
547 ResourceOwnerPassword, ResourceOwnerUsername, ResponseType, RevocationUrl, Scope, TokenUrl,
548 UserCode, VerificationUriComplete,
549};
550use std::error::Error;
551
552/// Public re-exports of types used for HTTP client interfaces.
553pub use http;
554pub use url;
555
556#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
557pub use ::curl;
558
559#[cfg(any(feature = "reqwest", feature = "reqwest-blocking"))]
560pub use ::reqwest;
561
562#[cfg(feature = "ureq")]
563pub use ::ureq;
564
565const CONTENT_TYPE_JSON: &str = "application/json";
566const CONTENT_TYPE_FORMENCODED: &str = "application/x-www-form-urlencoded";
567
568/// There was a problem configuring the request.
569#[non_exhaustive]
570#[derive(Debug, thiserror::Error)]
571pub enum ConfigurationError {
572 /// The endpoint URL is not set.
573 #[error("No {0} endpoint URL specified")]
574 MissingUrl(&'static str),
575 /// The endpoint URL to be contacted MUST be HTTPS.
576 #[error("Scheme for {0} endpoint URL must be HTTPS")]
577 InsecureUrl(&'static str),
578}
579
580/// Indicates whether requests to the authorization server should use basic authentication or
581/// include the parameters in the request body for requests in which either is valid.
582///
583/// The default AuthType is *BasicAuth*, following the recommendation of
584/// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1).
585#[derive(Clone, Debug)]
586#[non_exhaustive]
587pub enum AuthType {
588 /// The client_id and client_secret (if set) will be included as part of the request body.
589 RequestBody,
590 /// The client_id and client_secret will be included using the basic auth authentication scheme.
591 BasicAuth,
592}
593
594/// Error type returned by built-in HTTP clients when requests fail.
595#[non_exhaustive]
596#[derive(Debug, thiserror::Error)]
597pub enum HttpClientError<RE>
598where
599 RE: Error + 'static,
600{
601 /// Error returned by reqwest crate.
602 #[error("client error")]
603 Reqwest(#[from] Box<RE>),
604 /// Non-reqwest HTTP error.
605 #[error("HTTP error")]
606 Http(#[from] http::Error),
607 /// I/O error.
608 #[error("I/O error")]
609 Io(#[from] std::io::Error),
610 /// Other error.
611 #[error("{}", _0)]
612 Other(String),
613}