Skip to main content

axum_security_oauth2/http/
mod.rs

1#[cfg(feature = "reqwest")]
2pub(crate) mod dep_reqwest;
3
4use std::fmt;
5
6use url::Url;
7
8use crate::error::HttpError;
9
10/// The HTTP backend used for token requests.
11///
12/// A feature-gated enum rather than a trait: enabling a backend feature
13/// adds a variant, and the client stays free of type parameters. Backends
14/// must never follow redirects; the ones this crate constructs don't.
15///
16/// `Clone` is cheap — the reqwest backend shares one connection pool across
17/// clones — so a single backend can be handed to several clients (e.g.
18/// `axum-security-oidc` reuses the login client's backend for discovery and
19/// JWKS fetches).
20#[derive(Clone)]
21#[non_exhaustive]
22pub enum HttpClient {
23    #[cfg(feature = "reqwest")]
24    Reqwest(reqwest::Client),
25}
26
27#[cfg(feature = "reqwest")]
28impl HttpClient {
29    /// The default reqwest backend: redirects never followed, 10 second
30    /// timeout — the same client the builder installs when none is given.
31    pub fn default_reqwest() -> Self {
32        HttpClient::Reqwest(dep_reqwest::default_client())
33    }
34}
35
36#[cfg(feature = "reqwest")]
37impl From<reqwest::Client> for HttpClient {
38    fn from(client: reqwest::Client) -> Self {
39        HttpClient::Reqwest(client)
40    }
41}
42
43impl fmt::Debug for HttpClient {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        let _ = f;
46        match *self {
47            #[cfg(feature = "reqwest")]
48            HttpClient::Reqwest(_) => f.write_str("HttpClient::Reqwest(..)"),
49        }
50    }
51}
52
53/// What the protocol layer needs back from a backend: status, content type
54/// and the raw body.
55pub(crate) struct FormResponse {
56    pub(crate) status: u16,
57    pub(crate) content_type: Option<String>,
58    pub(crate) body: Vec<u8>,
59}
60
61/// The response from [`HttpClient::get`]: the HTTP status and the raw body.
62///
63/// A non-2xx status is not an error here — the caller decides. Reach for this
64/// to fetch small JSON documents (OIDC discovery metadata, a JWK set).
65pub struct HttpResponse {
66    /// The HTTP status code.
67    pub status: u16,
68    /// The raw response body.
69    pub body: Vec<u8>,
70}
71
72impl HttpResponse {
73    /// Whether the status is in the 2xx range.
74    pub fn is_success(&self) -> bool {
75        (200..300).contains(&self.status)
76    }
77}
78
79impl HttpClient {
80    /// POSTs `application/x-www-form-urlencoded` and returns the raw
81    /// response — the one seam every backend implements.
82    #[cfg_attr(not(feature = "reqwest"), allow(unused_variables))]
83    pub(crate) async fn post_form(
84        &self,
85        url: &Url,
86        form: &[(&str, &str)],
87        authorization: Option<&str>,
88    ) -> Result<FormResponse, HttpError> {
89        match *self {
90            #[cfg(feature = "reqwest")]
91            HttpClient::Reqwest(ref client) => {
92                dep_reqwest::post_form(client, url, form, authorization).await
93            }
94        }
95    }
96
97    /// GETs `url` with `Accept: application/json` and returns the status and
98    /// raw body. Redirects are not followed (the backends this crate builds
99    /// enforce that). Used by `axum-security-oidc` for discovery and JWKS.
100    #[cfg_attr(not(feature = "reqwest"), allow(unused_variables))]
101    pub async fn get(&self, url: &Url) -> Result<HttpResponse, HttpError> {
102        match *self {
103            #[cfg(feature = "reqwest")]
104            HttpClient::Reqwest(ref client) => dep_reqwest::get(client, url).await,
105        }
106    }
107}