Repository: ramosbugs/oauth2-rs (language: rust)

Issue:
Add ureq http provider
Hello,

I've recently started using oauth2-rs for my application (and it works nicely), but I'm using ureq instead of curl to cut the dependency on that. Would you be interested in me upstreaming that or should users just implement the `http_client` method themselves for ureq (it's only like ~40 LOC)?


Discussion / hints:
hey @Cogitri, I don't have any experience with ureq, but I'd be willing to merge a patch if you want to upstream it. just be sure to put it behind a (non-default) feature flag like the reqwest and curl clients. hopefully this won't introduce any versioning issues down the road!

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 1b9d78bb..b9f3cdc5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,6 +28,7 @@ reqwest = { version = "0.11", optional = true, features = ["blocking", "rustls-t
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 sha2 = "0.9"
+ureq = { version = "2", optional = true }
 url = { version = "2.1", features = ["serde"] }
 chrono = { version = "0.4", features = ["serde"] }
 
diff --git a/src/lib.rs b/src/lib.rs
index db1ddb5f..bcd339e7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -45,6 +45,11 @@
 //!
 //!    Synchronous client: [`curl::http_client`]
 //!
+//! * **[`ureq`]**
+//!
+//!    The `ureq` HTTP client is a simple HTTP client with minimal dependencies. It only supports
+//!    the synchronous HTTP client mode and can be enabled in `Cargo.toml` via the `ureq` feature flag.
+//!
 //!  * **Custom**
 //!
 //!    In addition to the clients above, users may define their own HTTP clients, which must accept
@@ -472,6 +477,13 @@ mod tests;
 
 mod types;
 
+///
+/// HTTP client backed by the [ureq](https://crates.io/crates/ureq) crate.
+/// Requires "ureq" feature.
+///
+#[cfg(feature = "ureq")]
+pub mod ureq;
+
 ///
 /// Public re-exports of types used for HTTP client interfaces.
 ///
diff --git a/src/ureq.rs b/src/ureq.rs
new file mode 100644
index 00000000..30446b7c
--- /dev/null
+++ b/src/ureq.rs
@@ -0,0 +1,73 @@
+use http::{
+    header::{HeaderMap, HeaderValue, CONTENT_TYPE},
+    method::Method,
+    status::StatusCode,
+};
+
+use super::{HttpRequest, HttpResponse};
+
+///
+/// Error type returned by failed ureq HTTP requests.
+///
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    /// Non-ureq HTTP error.
+    #[error("HTTP error")]
+    Http(#[from] http::Error),
+    /// IO error
+    #[error("IO error")]
+    IO(#[from] std::io::Error),
+    /// Other error.
+    #[error("Other error: {}", _0)]
+    Other(String),
+    /// Error returned by ureq crate.
+    // boxed due to https://github.com/algesten/ureq/issues/296
+    #[error("ureq request failed")]
+    Ureq(#[from] Box<ureq::Error>),
+}
+
+///
+/// Synchronous HTTP client for ureq.
+///
+pub fn http_client(request: HttpRequest) -> Result<HttpResponse, Error> {
+    let mut req = if let Method::POST = request.method {
+        ureq::post(&request.url.to_string())
+    } else {
+        ureq::get(&request.url.to_string())
+    };
+
+    for (name, value) in request.headers {
+        if let Some(name) = name {
+            req = req.set(
+                &name.to_string(),
+                value.to_str().map_err(|_| {
+                    Error::Other(format!(
+                        "invalid {} header value {:?}",
+                        name,
+                        value.as_bytes()
+                    ))
+                })?,
+            );
+        }
+    }
+
+    let response = if let Method::POST = request.method {
+        req.send(&*request.body)
+    } else {
+        req.call()
+    }
+    .map_err(Box::new)?;
+
+    Ok(HttpResponse {
+        status_code: StatusCode::from_u16(response.status())
+            .map_err(|err| Error::Http(err.into()))?,
+        headers: vec![(
+            CONTENT_TYPE,
+            HeaderValue::from_str(response.content_type())
+                .map_err(|err| Error::Http(err.into()))?,
+        )]
+        .into_iter()
+        .collect::<HeaderMap>(),
+        body: response.into_string()?.as_bytes().into(),
+    })
+}


Test patch: