diff --git a/README.md b/README.md index 6f5b37d..de401b5 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,29 @@ to allow for client middleware chains. [![CI](https://github.com/TrueLayer/reqwest-middleware/workflows/CI/badge.svg)](https://github.com/TrueLayer/reqwest-middleware/actions) [![Coverage Status](https://coveralls.io/repos/github/TrueLayer/reqwest-middleware/badge.svg?branch=main&t=YKhONc)](https://coveralls.io/github/TrueLayer/reqwest-middleware?branch=main) +This crate provides functionality for building and running middleware but no middleware +implementations. This repository also contains a couple of useful concrete middleware crates: + +* [`reqwest-retry`](https://crates.io/crates/reqwest-retry): retry failed requests. +* [`reqwest-trcing`](https://crates.io/crates/reqwest-tracing): + [`tracing`](https://crates.io/crates/tracing) integration, optional opentelemetry support. + ## Overview The `reqwest-middleware` client exposes the same interface as a plain `reqwest` client, but `ClientBuilder` exposes functionality to attach middleware: +```toml +# Cargo.toml +# ... +[dependencies] +reqwest = "0.11" +reqwest-middleware = "0.1.1" +reqwest-retry = "0.1.1" +reqwest-tracing = "0.1.2" +tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } +``` + ```rust use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; @@ -20,35 +38,27 @@ use reqwest_tracing::TracingMiddleware; #[tokio::main] async fn main() { -    let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); -    let client = ClientBuilder::new(reqwest::Client::new()) -        .with(TracingMiddleware) -        .with(RetryTransientMiddleware::new_with_policy(retry_policy)) -        .build(); -    run(client).await; + // Retry up to 3 times with increasing intervals between attempts. + let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); + let client = ClientBuilder::new(reqwest::Client::new()) + // Trace HTTP requests. See the tracing crate to make use of these traces. + .with(TracingMiddleware) + // Retry failed requests. + .with(RetryTransientMiddleware::new_with_policy(retry_policy)) + .build(); + run(client).await; } async fn run(client: ClientWithMiddleware) { -    // free retries! -    client -        .get("https://some-external-service.com") -        .header("foo", "bar") -        .send() -        .await -        .unwrap(); + client + .get("https://truelayer.com") + .header("foo", "bar") + .send() + .await + .unwrap(); } ``` -## How to install - -Add `reqwest-middleware` to your dependencies - -```toml -[dependencies] -# ... -reqwest-middleware = "0.1.0" -``` - #### License diff --git a/reqwest-middleware/Cargo.toml b/reqwest-middleware/Cargo.toml index 4337c7b..8e53070 100644 --- a/reqwest-middleware/Cargo.toml +++ b/reqwest-middleware/Cargo.toml @@ -21,5 +21,8 @@ thiserror = "1" truelayer-extensions = "0.1" [dev-dependencies] +reqwest = "0.11" +reqwest-retry = { path = "../reqwest-retry" } +reqwest-tracing = { path = "../reqwest-tracing" } wiremock = "0.5" -tokio = { version = "1", features = ["macros"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/reqwest-middleware/src/client.rs b/reqwest-middleware/src/client.rs index a629c34..450e68a 100644 --- a/reqwest-middleware/src/client.rs +++ b/reqwest-middleware/src/client.rs @@ -13,7 +13,7 @@ use crate::middleware::{Middleware, Next}; /// A `ClientBuilder` is used to build a [`ClientWithMiddleware`]. /// -/// [`ClientWithMiddleware`]: struct.ClientWithMiddleware.html +/// [`ClientWithMiddleware`]: crate::ClientWithMiddleware pub struct ClientBuilder { client: Client, middleware_stack: Vec>, @@ -31,7 +31,7 @@ impl ClientBuilder { /// /// If you need to keep a reference to the middleware after attaching, use [`with_arc`]. /// - /// [`with_arc`]: #method.with_arc + /// [`with_arc`]: Self::with_arc pub fn with(self, middleware: M) -> Self where M: Middleware, @@ -41,7 +41,7 @@ impl ClientBuilder { /// Add middleware to the chain. [`with`] is more ergonomic if you don't need the `Arc`. /// - /// [`with`]: #method.with + /// [`with`]: Self::with pub fn with_arc(mut self, middleware: Arc) -> Self { self.middleware_stack.push(middleware); self @@ -55,8 +55,6 @@ impl ClientBuilder { /// `ClientWithMiddleware` is a wrapper around [`reqwest::Client`] which runs middleware on every /// request. -/// -/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.8/reqwest/struct.Client.html #[derive(Clone)] pub struct ClientWithMiddleware { inner: reqwest::Client, @@ -65,8 +63,6 @@ pub struct ClientWithMiddleware { impl ClientWithMiddleware { /// See [`ClientBuilder`] for a more ergonomic way to build `ClientWithMiddleware` instances. - /// - /// [`ClientBuilder`]: struct.ClientBuilder.html pub fn new(client: Client, middleware_stack: T) -> Self where T: Into]>>, @@ -77,40 +73,37 @@ impl ClientWithMiddleware { } } - /// See [`Client::get`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.get) + /// See [`Client::get`] pub fn get(&self, url: U) -> RequestBuilder { self.request(Method::GET, url) } - /// See [`Client::post`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.post) + /// See [`Client::post`] pub fn post(&self, url: U) -> RequestBuilder { self.request(Method::POST, url) } - /// See [`Client::put`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.put) + /// See [`Client::put`] pub fn put(&self, url: U) -> RequestBuilder { self.request(Method::PUT, url) } - /// See - /// [`Client::patch`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.patch) + /// See [`Client::patch`] pub fn patch(&self, url: U) -> RequestBuilder { self.request(Method::PATCH, url) } - /// See - /// [`Client::delete`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.delete) + /// See [`Client::delete`] pub fn delete(&self, url: U) -> RequestBuilder { self.request(Method::DELETE, url) } - /// See [`Client::head`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.head) + /// See [`Client::head`] pub fn head(&self, url: U) -> RequestBuilder { self.request(Method::HEAD, url) } - /// See - /// [`Client::request`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.request) + /// See [`Client::request`] pub fn request(&self, method: Method, url: U) -> RequestBuilder { RequestBuilder { inner: self.inner.request(method, url), @@ -118,8 +111,7 @@ impl ClientWithMiddleware { } } - /// See - /// [`Client::execute`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.execute) + /// See [`Client::execute`] pub async fn execute(&self, req: Request) -> Result { let mut ext = Extensions::new(); self.execute_with_extensions(req, &mut ext).await @@ -147,8 +139,6 @@ impl From for ClientWithMiddleware { } /// This is a wrapper around [`reqwest::RequestBuilder`] exposing the same API. -/// -/// [`reqwest::RequestBuilder`]: https://docs.rs/reqwest/0.10.8/reqwest/struct.RequestBuilder.html #[must_use = "RequestBuilder does nothing until you 'send' it"] pub struct RequestBuilder { inner: reqwest::RequestBuilder, diff --git a/reqwest-middleware/src/lib.rs b/reqwest-middleware/src/lib.rs index 28531cc..efc0590 100644 --- a/reqwest-middleware/src/lib.rs +++ b/reqwest-middleware/src/lib.rs @@ -37,11 +37,18 @@ //! } //! ``` //! -//! [`build`]: struct.ClientBuilder.html#method.build -//! [`ClientBuilder`]: struct.ClientBuilder.html -//! [`ClientWithMiddleware`]: struct.ClientWithMiddleware.html -//! [`reqwest::Client`]: https://docs.rs/reqwest/0.10.8/reqwest/struct.Client.html -//! [`with`]: struct.ClientBuilder.html#method.with +//! [`build`]: ClientBuilder::build +//! [`ClientBuilder`]: ClientBuilder +//! [`ClientWithMiddleware`]: ClientWithMiddleware +//! [`with`]: ClientBuilder::with + +// Test README examples without overriding module docs. +// We want to keep the in-code docs separate as those allow for automatic linking to crate +// documentation. +#[doc = include_str!("../../README.md")] +#[cfg(doctest)] +pub struct ReadmeDoctests; + mod client; mod error; mod middleware; diff --git a/reqwest-middleware/src/middleware.rs b/reqwest-middleware/src/middleware.rs index 52a8de0..926c601 100644 --- a/reqwest-middleware/src/middleware.rs +++ b/reqwest-middleware/src/middleware.rs @@ -30,9 +30,8 @@ use crate::error::{Error, Result}; /// } /// ``` /// -/// [`ClientWithMiddleware`]: struct.ClientWithMiddleware.html -/// [`Extensions`]: TODO -/// [`with`]: struct.ClientBuilder.html#method.with +/// [`ClientWithMiddleware`]: crate::ClientWithMiddleware +/// [`with`]: crate::ClientBuilder::with #[async_trait::async_trait] pub trait Middleware: 'static + Send + Sync { /// Invoked with a request before sending it. If you want to continue processing the request, @@ -69,8 +68,8 @@ where /// Next encapsulates the remaining middleware chain to run in [`Middleware::handle`]. You can /// forward the request down the chain with [`run`]. /// -/// [`Middleware::handle`]: trait.Middleware.html#tymethod.handle -/// [`run`]: #method.run +/// [`Middleware::handle`]: Middleware::handle +/// [`run`]: Self::run #[derive(Clone)] pub struct Next<'a> { client: &'a Client, diff --git a/reqwest-retry/CHANGELOG.md b/reqwest-retry/CHANGELOG.md index cb5548b..0308796 100644 --- a/reqwest-retry/CHANGELOG.md +++ b/reqwest-retry/CHANGELOG.md @@ -5,7 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - +### Added +- Re-export `RetryPolicy` from the crate root. ### Changed - Disabled default features on `reqwest` diff --git a/reqwest-retry/README.md b/reqwest-retry/README.md index 9376295..54012ab 100644 --- a/reqwest-retry/README.md +++ b/reqwest-retry/README.md @@ -15,15 +15,7 @@ Build `RetryTransientMiddleware` from a `RetryPolicy`, then attach it to a [`retry-policies::policies`](https://crates.io/crates/retry-policies) is reexported under `reqwest_retry::policies` for convenience. -## How to install - -Add `reqwest-retry` to your dependencies - -```toml -[dependencies] -# ... -reqwest-retry = "0.1.0" -``` +See [`reqwest_middleware`](https://docs.rs/reqwest_middleware) for usage with reqwest. #### License diff --git a/reqwest-retry/src/lib.rs b/reqwest-retry/src/lib.rs index 06130ab..bef3763 100644 --- a/reqwest-retry/src/lib.rs +++ b/reqwest-retry/src/lib.rs @@ -1,7 +1,34 @@ +//! Middleware to retry failed HTTP requests built on [`reqwest_middleware`]. +//! +//! Use [`RetryTransientMiddleware`] to retry failed HTTP requests. Retry control flow is managed +//! by a [`RetryPolicy`]. +//! +//! ## Example +//! +//! ``` +//! use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +//! use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; +//! +//! async fn run_retries() { +//! // Retry up to 3 times with increasing intervals between attempts. +//! let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); +//! let client = ClientBuilder::new(reqwest::Client::new()) +//! .with(RetryTransientMiddleware::new_with_policy(retry_policy)) +//! .build(); +//! +//! client +//! .get("https://truelayer.com") +//! .header("foo", "bar") +//! .send() +//! .await +//! .unwrap(); +//! } +//! ``` + mod middleware; mod retryable; -pub use retry_policies::policies; +pub use retry_policies::{policies, RetryPolicy}; pub use middleware::RetryTransientMiddleware; pub use retryable::Retryable; diff --git a/reqwest-tracing/README.md b/reqwest-tracing/README.md index 7a1b4f6..2822e21 100644 --- a/reqwest-tracing/README.md +++ b/reqwest-tracing/README.md @@ -12,8 +12,23 @@ Opentracing middleware implementation for Attach `TracingMiddleware` to your client to automatically trace HTTP requests: -```rust -use opentelemetry::exporter::trace::stdout; +```toml +# Cargo.toml +# ... +[dependencies] +opentelemetry = "0.16" +reqwest = "0.11" +reqwest-middleware = "0.1.1" +reqwest-retry = "0.1.1" +reqwest-tracing = { version = "0.1.2", features = ["opentelemetry_0_16"] } +tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } +tracing = "0.1" +tracing-opentelemetry = "0.15" +tracing-subscriber = "0.2" +``` + +```rust,skip +use opentelemetry::sdk::export::trace::stdout; use reqwest_middleware::ClientBuilder; use reqwest_tracing::TracingMiddleware; use tracing_subscriber::layer::SubscriberExt; @@ -21,7 +36,7 @@ use tracing_subscriber::Registry; #[tokio::main] async fn main() { - let (tracer, _) = stdout::new_pipeline().install(); + let tracer = stdout::new_pipeline().install_simple(); let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); let subscriber = Registry::default().with(telemetry); tracing::subscriber::set_global_default(subscriber).unwrap(); @@ -29,15 +44,20 @@ async fn main() { run().await; } -async fun run() { +async fn run() { let client = ClientBuilder::new(reqwest::Client::new()) .with(TracingMiddleware) - .build();` + .build(); client.get("https://truelayer.com").send().await.unwrap(); } ``` +```terminal +$ cargo run +SpanData { span_context: SpanContext { trace_id: ... +``` + See the [`tracing`](https://crates.io/crates/tracing) crate for more information on how to set up a tracing subscriber to make use of the spans. diff --git a/reqwest-tracing/src/lib.rs b/reqwest-tracing/src/lib.rs index c17d90f..d7ebb45 100644 --- a/reqwest-tracing/src/lib.rs +++ b/reqwest-tracing/src/lib.rs @@ -1,3 +1,7 @@ +//! Opentracing middleware implementation for [`reqwest-middleware`]. +//! +//! Attach [`TracingMiddleware`] to your client to automatically trace HTTP requests. + mod middleware; #[cfg(any( feature = "opentelemetry_0_13",