mirror of
https://github.com/TrueLayer/reqwest-middleware.git
synced 2024-12-27 11:26:31 +00:00
extensions middleware and request extensions (#56)
* extensions middleware and request extensions * fix publish * request initialisers * simplify example * docs * changelog * Apply suggestions from code review Co-authored-by: Alex Butler <alexheretic@gmail.com> Co-authored-by: Alex Butler <alexheretic@gmail.com>
This commit is contained in:
parent
3c00388cfb
commit
85e520f78b
4 changed files with 172 additions and 21 deletions
|
@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Implementation of `Debug` trait for `RequestBuilder`.
|
- Implementation of `Debug` trait for `RequestBuilder`.
|
||||||
|
- A new `RequestInitialiser` trait that can be added to `ClientWithMiddleware`
|
||||||
|
- A new `Extension` initialiser that adds extensions to the request
|
||||||
|
- Adds `with_extension` method functionality to `RequestBuilder` that can add extensions for the `send` method to use - deprecating `send_with_extensions`.
|
||||||
|
|
||||||
|
## [0.1.6] - 2022-04-21
|
||||||
|
|
||||||
|
Absolutely nothing changed
|
||||||
|
|
||||||
## [0.1.5] - 2022-02-21
|
## [0.1.5] - 2022-02-21
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use task_local_extensions::Extensions;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::middleware::{Middleware, Next};
|
use crate::middleware::{Middleware, Next};
|
||||||
|
use crate::RequestInitialiser;
|
||||||
|
|
||||||
/// A `ClientBuilder` is used to build a [`ClientWithMiddleware`].
|
/// A `ClientBuilder` is used to build a [`ClientWithMiddleware`].
|
||||||
///
|
///
|
||||||
|
@ -17,6 +18,7 @@ use crate::middleware::{Middleware, Next};
|
||||||
pub struct ClientBuilder {
|
pub struct ClientBuilder {
|
||||||
client: Client,
|
client: Client,
|
||||||
middleware_stack: Vec<Arc<dyn Middleware>>,
|
middleware_stack: Vec<Arc<dyn Middleware>>,
|
||||||
|
initialiser_stack: Vec<Arc<dyn RequestInitialiser>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientBuilder {
|
impl ClientBuilder {
|
||||||
|
@ -24,6 +26,7 @@ impl ClientBuilder {
|
||||||
ClientBuilder {
|
ClientBuilder {
|
||||||
client,
|
client,
|
||||||
middleware_stack: Vec::new(),
|
middleware_stack: Vec::new(),
|
||||||
|
initialiser_stack: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +50,33 @@ impl ClientBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience method to attach a request initialiser.
|
||||||
|
///
|
||||||
|
/// If you need to keep a reference to the initialiser after attaching, use [`with_arc_init`].
|
||||||
|
///
|
||||||
|
/// [`with_arc_init`]: Self::with_arc_init
|
||||||
|
pub fn with_init<I>(self, initialiser: I) -> Self
|
||||||
|
where
|
||||||
|
I: RequestInitialiser,
|
||||||
|
{
|
||||||
|
self.with_arc_init(Arc::new(initialiser))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a request initialiser to the chain. [`with_init`] is more ergonomic if you don't need the `Arc`.
|
||||||
|
///
|
||||||
|
/// [`with_init`]: Self::with_init
|
||||||
|
pub fn with_arc_init(mut self, initialiser: Arc<dyn RequestInitialiser>) -> Self {
|
||||||
|
self.initialiser_stack.push(initialiser);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a `ClientWithMiddleware` using this builder configuration.
|
/// Returns a `ClientWithMiddleware` using this builder configuration.
|
||||||
pub fn build(self) -> ClientWithMiddleware {
|
pub fn build(self) -> ClientWithMiddleware {
|
||||||
ClientWithMiddleware::new(self.client, self.middleware_stack)
|
ClientWithMiddleware {
|
||||||
|
inner: self.client,
|
||||||
|
middleware_stack: self.middleware_stack.into_boxed_slice(),
|
||||||
|
initialiser_stack: self.initialiser_stack.into_boxed_slice(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +86,7 @@ impl ClientBuilder {
|
||||||
pub struct ClientWithMiddleware {
|
pub struct ClientWithMiddleware {
|
||||||
inner: reqwest::Client,
|
inner: reqwest::Client,
|
||||||
middleware_stack: Box<[Arc<dyn Middleware>]>,
|
middleware_stack: Box<[Arc<dyn Middleware>]>,
|
||||||
|
initialiser_stack: Box<[Arc<dyn RequestInitialiser>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientWithMiddleware {
|
impl ClientWithMiddleware {
|
||||||
|
@ -70,6 +98,8 @@ impl ClientWithMiddleware {
|
||||||
ClientWithMiddleware {
|
ClientWithMiddleware {
|
||||||
inner: client,
|
inner: client,
|
||||||
middleware_stack: middleware_stack.into(),
|
middleware_stack: middleware_stack.into(),
|
||||||
|
// TODO(conradludgate) - allow downstream code to control this manually if desired
|
||||||
|
initialiser_stack: Box::new([]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,10 +135,14 @@ impl ClientWithMiddleware {
|
||||||
|
|
||||||
/// See [`Client::request`]
|
/// See [`Client::request`]
|
||||||
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
||||||
RequestBuilder {
|
let req = RequestBuilder {
|
||||||
inner: self.inner.request(method, url),
|
inner: self.inner.request(method, url),
|
||||||
client: self.clone(),
|
client: self.clone(),
|
||||||
}
|
extensions: Extensions::new(),
|
||||||
|
};
|
||||||
|
self.initialiser_stack
|
||||||
|
.iter()
|
||||||
|
.fold(req, |req, i| i.init(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [`Client::execute`]
|
/// See [`Client::execute`]
|
||||||
|
@ -134,6 +168,7 @@ impl From<Client> for ClientWithMiddleware {
|
||||||
ClientWithMiddleware {
|
ClientWithMiddleware {
|
||||||
inner: client,
|
inner: client,
|
||||||
middleware_stack: Box::new([]),
|
middleware_stack: Box::new([]),
|
||||||
|
initialiser_stack: Box::new([]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,6 +187,7 @@ impl fmt::Debug for ClientWithMiddleware {
|
||||||
pub struct RequestBuilder {
|
pub struct RequestBuilder {
|
||||||
inner: reqwest::RequestBuilder,
|
inner: reqwest::RequestBuilder,
|
||||||
client: ClientWithMiddleware,
|
client: ClientWithMiddleware,
|
||||||
|
extensions: Extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestBuilder {
|
impl RequestBuilder {
|
||||||
|
@ -164,14 +200,14 @@ impl RequestBuilder {
|
||||||
{
|
{
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.header(key, value),
|
inner: self.inner.header(key, value),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn headers(self, headers: HeaderMap) -> Self {
|
pub fn headers(self, headers: HeaderMap) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.headers(headers),
|
inner: self.inner.headers(headers),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +218,7 @@ impl RequestBuilder {
|
||||||
{
|
{
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.basic_auth(username, password),
|
inner: self.inner.basic_auth(username, password),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,49 +228,49 @@ impl RequestBuilder {
|
||||||
{
|
{
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.bearer_auth(token),
|
inner: self.inner.bearer_auth(token),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn body<T: Into<Body>>(self, body: T) -> Self {
|
pub fn body<T: Into<Body>>(self, body: T) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.body(body),
|
inner: self.inner.body(body),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timeout(self, timeout: Duration) -> Self {
|
pub fn timeout(self, timeout: Duration) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.timeout(timeout),
|
inner: self.inner.timeout(timeout),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn multipart(self, multipart: Form) -> Self {
|
pub fn multipart(self, multipart: Form) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.multipart(multipart),
|
inner: self.inner.multipart(multipart),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query<T: Serialize + ?Sized>(self, query: &T) -> Self {
|
pub fn query<T: Serialize + ?Sized>(self, query: &T) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.query(query),
|
inner: self.inner.query(query),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn form<T: Serialize + ?Sized>(self, form: &T) -> Self {
|
pub fn form<T: Serialize + ?Sized>(self, form: &T) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.form(form),
|
inner: self.inner.form(form),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn json<T: Serialize + ?Sized>(self, json: &T) -> Self {
|
pub fn json<T: Serialize + ?Sized>(self, json: &T) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
inner: self.inner.json(json),
|
inner: self.inner.json(json),
|
||||||
client: self.client,
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,22 +278,44 @@ impl RequestBuilder {
|
||||||
self.inner.build()
|
self.inner.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts the extension into this request builder
|
||||||
|
pub fn with_extension<T: Send + Sync + 'static>(mut self, extension: T) -> Self {
|
||||||
|
self.extensions.insert(extension);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the internal set of extensions for this request
|
||||||
|
pub fn extensions(&mut self) -> &mut Extensions {
|
||||||
|
&mut self.extensions
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send(self) -> Result<Response> {
|
pub async fn send(self) -> Result<Response> {
|
||||||
let req = self.inner.build()?;
|
let Self {
|
||||||
self.client.execute(req).await
|
inner,
|
||||||
|
client,
|
||||||
|
mut extensions,
|
||||||
|
} = self;
|
||||||
|
let req = inner.build()?;
|
||||||
|
client.execute_with_extensions(req, &mut extensions).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a request with initial [`Extensions`].
|
/// Sends a request with initial [`Extensions`].
|
||||||
|
#[deprecated = "use the with_extension method and send directly"]
|
||||||
pub async fn send_with_extensions(self, ext: &mut Extensions) -> Result<Response> {
|
pub async fn send_with_extensions(self, ext: &mut Extensions) -> Result<Response> {
|
||||||
let req = self.inner.build()?;
|
let Self { inner, client, .. } = self;
|
||||||
self.client.execute_with_extensions(req, ext).await
|
let req = inner.build()?;
|
||||||
|
client.execute_with_extensions(req, ext).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(conradludgate): fix this method to take `&self`. It's currently useless as it is.
|
||||||
|
// I'm tempted to make this breaking change without a major bump, but I'll wait for now
|
||||||
|
#[deprecated = "This method was badly replicated from the base RequestBuilder. If you somehow made use of this method, it will break next major version"]
|
||||||
pub fn try_clone(self) -> Option<Self> {
|
pub fn try_clone(self) -> Option<Self> {
|
||||||
let client = self.client;
|
self.inner.try_clone().map(|inner| RequestBuilder {
|
||||||
self.inner
|
inner,
|
||||||
.try_clone()
|
client: self.client,
|
||||||
.map(|inner| RequestBuilder { inner, client })
|
extensions: self.extensions,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,9 @@ pub struct ReadmeDoctests;
|
||||||
mod client;
|
mod client;
|
||||||
mod error;
|
mod error;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
|
mod req_init;
|
||||||
|
|
||||||
pub use client::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
pub use client::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use middleware::{Middleware, Next};
|
pub use middleware::{Middleware, Next};
|
||||||
|
pub use req_init::{Extension, RequestInitialiser};
|
||||||
|
|
84
reqwest-middleware/src/req_init.rs
Normal file
84
reqwest-middleware/src/req_init.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use crate::RequestBuilder;
|
||||||
|
|
||||||
|
/// When attached to a [`ClientWithMiddleware`] (generally using [`with_init`]), it is run
|
||||||
|
/// whenever the client starts building a request, in the order it was attached.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use reqwest_middleware::{RequestInitialiser, RequestBuilder};
|
||||||
|
///
|
||||||
|
/// struct AuthInit;
|
||||||
|
///
|
||||||
|
/// impl RequestInitialiser for AuthInit {
|
||||||
|
/// fn init(&self, req: RequestBuilder) -> RequestBuilder {
|
||||||
|
/// req.bearer_auth("my_auth_token")
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`ClientWithMiddleware`]: crate::ClientWithMiddleware
|
||||||
|
/// [`with_init`]: crate::ClientBuilder::with_init
|
||||||
|
pub trait RequestInitialiser: 'static + Send + Sync {
|
||||||
|
fn init(&self, req: RequestBuilder) -> RequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> RequestInitialiser for F
|
||||||
|
where
|
||||||
|
F: Send + Sync + 'static + Fn(RequestBuilder) -> RequestBuilder,
|
||||||
|
{
|
||||||
|
fn init(&self, req: RequestBuilder) -> RequestBuilder {
|
||||||
|
(self)(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A middleware that inserts the value into the [`Extensions`](task_local_extensions::Extensions) during the call.
|
||||||
|
///
|
||||||
|
/// This is a good way to inject extensions to middleware deeper in the stack
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use reqwest::{Client, Request, Response};
|
||||||
|
/// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result, Extension};
|
||||||
|
/// use task_local_extensions::Extensions;
|
||||||
|
///
|
||||||
|
/// #[derive(Clone)]
|
||||||
|
/// struct LogName(&'static str);
|
||||||
|
/// struct LoggingMiddleware;
|
||||||
|
///
|
||||||
|
/// #[async_trait::async_trait]
|
||||||
|
/// impl Middleware for LoggingMiddleware {
|
||||||
|
/// async fn handle(
|
||||||
|
/// &self,
|
||||||
|
/// req: Request,
|
||||||
|
/// extensions: &mut Extensions,
|
||||||
|
/// next: Next<'_>,
|
||||||
|
/// ) -> Result<Response> {
|
||||||
|
/// // get the log name or default to "unknown"
|
||||||
|
/// let name = extensions
|
||||||
|
/// .get()
|
||||||
|
/// .map(|&LogName(name)| name)
|
||||||
|
/// .unwrap_or("unknown");
|
||||||
|
/// println!("[{name}] Request started {req:?}");
|
||||||
|
/// let res = next.run(req, extensions).await;
|
||||||
|
/// println!("[{name}] Result: {res:?}");
|
||||||
|
/// res
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// async fn run() {
|
||||||
|
/// let reqwest_client = Client::builder().build().unwrap();
|
||||||
|
/// let client = ClientBuilder::new(reqwest_client)
|
||||||
|
/// .with_init(Extension(LogName("my-client")))
|
||||||
|
/// .with(LoggingMiddleware)
|
||||||
|
/// .build();
|
||||||
|
/// let resp = client.get("https://truelayer.com").send().await.unwrap();
|
||||||
|
/// println!("TrueLayer page HTML: {}", resp.text().await.unwrap());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Extension<T>(pub T);
|
||||||
|
|
||||||
|
impl<T: Send + Sync + Clone + 'static> RequestInitialiser for Extension<T> {
|
||||||
|
fn init(&self, req: RequestBuilder) -> RequestBuilder {
|
||||||
|
req.with_extension(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue