Include README in crate doctests and improve documentation (#8)

* chore: Improve docs and include some examples in doctests
This commit is contained in:
tl-rodrigo-gryzinski 2021-09-28 19:26:03 +01:00 committed by GitHub
parent e0b01383d6
commit b8645f81eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 124 additions and 71 deletions

View file

@ -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) [![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) [![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 ## Overview
The `reqwest-middleware` client exposes the same interface as a plain `reqwest` client, but The `reqwest-middleware` client exposes the same interface as a plain `reqwest` client, but
`ClientBuilder` exposes functionality to attach middleware: `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 ```rust
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff};
@ -20,35 +38,27 @@ use reqwest_tracing::TracingMiddleware;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
    let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); // Retry up to 3 times with increasing intervals between attempts.
    let client = ClientBuilder::new(reqwest::Client::new()) let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
        .with(TracingMiddleware) let client = ClientBuilder::new(reqwest::Client::new())
        .with(RetryTransientMiddleware::new_with_policy(retry_policy)) // Trace HTTP requests. See the tracing crate to make use of these traces.
        .build(); .with(TracingMiddleware)
    run(client).await; // Retry failed requests.
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
.build();
run(client).await;
} }
async fn run(client: ClientWithMiddleware) { async fn run(client: ClientWithMiddleware) {
    // free retries! client
    client .get("https://truelayer.com")
        .get("https://some-external-service.com") .header("foo", "bar")
        .header("foo", "bar") .send()
        .send() .await
        .await .unwrap();
        .unwrap();
} }
``` ```
## How to install
Add `reqwest-middleware` to your dependencies
```toml
[dependencies]
# ...
reqwest-middleware = "0.1.0"
```
#### License #### License
<sup> <sup>

View file

@ -21,5 +21,8 @@ thiserror = "1"
truelayer-extensions = "0.1" truelayer-extensions = "0.1"
[dev-dependencies] [dev-dependencies]
reqwest = "0.11"
reqwest-retry = { path = "../reqwest-retry" }
reqwest-tracing = { path = "../reqwest-tracing" }
wiremock = "0.5" wiremock = "0.5"
tokio = { version = "1", features = ["macros"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

View file

@ -13,7 +13,7 @@ use crate::middleware::{Middleware, Next};
/// A `ClientBuilder` is used to build a [`ClientWithMiddleware`]. /// A `ClientBuilder` is used to build a [`ClientWithMiddleware`].
/// ///
/// [`ClientWithMiddleware`]: struct.ClientWithMiddleware.html /// [`ClientWithMiddleware`]: crate::ClientWithMiddleware
pub struct ClientBuilder { pub struct ClientBuilder {
client: Client, client: Client,
middleware_stack: Vec<Arc<dyn Middleware>>, middleware_stack: Vec<Arc<dyn Middleware>>,
@ -31,7 +31,7 @@ impl ClientBuilder {
/// ///
/// If you need to keep a reference to the middleware after attaching, use [`with_arc`]. /// 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<M>(self, middleware: M) -> Self pub fn with<M>(self, middleware: M) -> Self
where where
M: Middleware, M: Middleware,
@ -41,7 +41,7 @@ impl ClientBuilder {
/// Add middleware to the chain. [`with`] is more ergonomic if you don't need the `Arc`. /// 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<dyn Middleware>) -> Self { pub fn with_arc(mut self, middleware: Arc<dyn Middleware>) -> Self {
self.middleware_stack.push(middleware); self.middleware_stack.push(middleware);
self self
@ -55,8 +55,6 @@ impl ClientBuilder {
/// `ClientWithMiddleware` is a wrapper around [`reqwest::Client`] which runs middleware on every /// `ClientWithMiddleware` is a wrapper around [`reqwest::Client`] which runs middleware on every
/// request. /// request.
///
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.8/reqwest/struct.Client.html
#[derive(Clone)] #[derive(Clone)]
pub struct ClientWithMiddleware { pub struct ClientWithMiddleware {
inner: reqwest::Client, inner: reqwest::Client,
@ -65,8 +63,6 @@ pub struct ClientWithMiddleware {
impl ClientWithMiddleware { impl ClientWithMiddleware {
/// See [`ClientBuilder`] for a more ergonomic way to build `ClientWithMiddleware` instances. /// See [`ClientBuilder`] for a more ergonomic way to build `ClientWithMiddleware` instances.
///
/// [`ClientBuilder`]: struct.ClientBuilder.html
pub fn new<T>(client: Client, middleware_stack: T) -> Self pub fn new<T>(client: Client, middleware_stack: T) -> Self
where where
T: Into<Box<[Arc<dyn Middleware>]>>, T: Into<Box<[Arc<dyn Middleware>]>>,
@ -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<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::GET, url) 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<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::POST, url) 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<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PUT, url) self.request(Method::PUT, url)
} }
/// See /// See [`Client::patch`]
/// [`Client::patch`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.patch)
pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PATCH, url) self.request(Method::PATCH, url)
} }
/// See /// See [`Client::delete`]
/// [`Client::delete`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.delete)
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::DELETE, url) 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<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::HEAD, url) self.request(Method::HEAD, url)
} }
/// See /// See [`Client::request`]
/// [`Client::request`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.request)
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder { pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
RequestBuilder { RequestBuilder {
inner: self.inner.request(method, url), inner: self.inner.request(method, url),
@ -118,8 +111,7 @@ impl ClientWithMiddleware {
} }
} }
/// See /// See [`Client::execute`]
/// [`Client::execute`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html#method.execute)
pub async fn execute(&self, req: Request) -> Result<Response> { pub async fn execute(&self, req: Request) -> Result<Response> {
let mut ext = Extensions::new(); let mut ext = Extensions::new();
self.execute_with_extensions(req, &mut ext).await self.execute_with_extensions(req, &mut ext).await
@ -147,8 +139,6 @@ impl From<Client> for ClientWithMiddleware {
} }
/// This is a wrapper around [`reqwest::RequestBuilder`] exposing the same API. /// 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"] #[must_use = "RequestBuilder does nothing until you 'send' it"]
pub struct RequestBuilder { pub struct RequestBuilder {
inner: reqwest::RequestBuilder, inner: reqwest::RequestBuilder,

View file

@ -37,11 +37,18 @@
//! } //! }
//! ``` //! ```
//! //!
//! [`build`]: struct.ClientBuilder.html#method.build //! [`build`]: ClientBuilder::build
//! [`ClientBuilder`]: struct.ClientBuilder.html //! [`ClientBuilder`]: ClientBuilder
//! [`ClientWithMiddleware`]: struct.ClientWithMiddleware.html //! [`ClientWithMiddleware`]: ClientWithMiddleware
//! [`reqwest::Client`]: https://docs.rs/reqwest/0.10.8/reqwest/struct.Client.html //! [`with`]: ClientBuilder::with
//! [`with`]: struct.ClientBuilder.html#method.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 client;
mod error; mod error;
mod middleware; mod middleware;

View file

@ -30,9 +30,8 @@ use crate::error::{Error, Result};
/// } /// }
/// ``` /// ```
/// ///
/// [`ClientWithMiddleware`]: struct.ClientWithMiddleware.html /// [`ClientWithMiddleware`]: crate::ClientWithMiddleware
/// [`Extensions`]: TODO /// [`with`]: crate::ClientBuilder::with
/// [`with`]: struct.ClientBuilder.html#method.with
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Middleware: 'static + Send + Sync { pub trait Middleware: 'static + Send + Sync {
/// Invoked with a request before sending it. If you want to continue processing the request, /// 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 /// Next encapsulates the remaining middleware chain to run in [`Middleware::handle`]. You can
/// forward the request down the chain with [`run`]. /// forward the request down the chain with [`run`].
/// ///
/// [`Middleware::handle`]: trait.Middleware.html#tymethod.handle /// [`Middleware::handle`]: Middleware::handle
/// [`run`]: #method.run /// [`run`]: Self::run
#[derive(Clone)] #[derive(Clone)]
pub struct Next<'a> { pub struct Next<'a> {
client: &'a Client, client: &'a Client,

View file

@ -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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
- Re-export `RetryPolicy` from the crate root.
### Changed ### Changed
- Disabled default features on `reqwest` - Disabled default features on `reqwest`

View file

@ -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 [`retry-policies::policies`](https://crates.io/crates/retry-policies) is reexported under
`reqwest_retry::policies` for convenience. `reqwest_retry::policies` for convenience.
## How to install See [`reqwest_middleware`](https://docs.rs/reqwest_middleware) for usage with reqwest.
Add `reqwest-retry` to your dependencies
```toml
[dependencies]
# ...
reqwest-retry = "0.1.0"
```
#### License #### License

View file

@ -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 middleware;
mod retryable; mod retryable;
pub use retry_policies::policies; pub use retry_policies::{policies, RetryPolicy};
pub use middleware::RetryTransientMiddleware; pub use middleware::RetryTransientMiddleware;
pub use retryable::Retryable; pub use retryable::Retryable;

View file

@ -12,8 +12,23 @@ Opentracing middleware implementation for
Attach `TracingMiddleware` to your client to automatically trace HTTP requests: Attach `TracingMiddleware` to your client to automatically trace HTTP requests:
```rust ```toml
use opentelemetry::exporter::trace::stdout; # 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_middleware::ClientBuilder;
use reqwest_tracing::TracingMiddleware; use reqwest_tracing::TracingMiddleware;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
@ -21,7 +36,7 @@ use tracing_subscriber::Registry;
#[tokio::main] #[tokio::main]
async fn 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 telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
let subscriber = Registry::default().with(telemetry); let subscriber = Registry::default().with(telemetry);
tracing::subscriber::set_global_default(subscriber).unwrap(); tracing::subscriber::set_global_default(subscriber).unwrap();
@ -29,15 +44,20 @@ async fn main() {
run().await; run().await;
} }
async fun run() { async fn run() {
let client = ClientBuilder::new(reqwest::Client::new()) let client = ClientBuilder::new(reqwest::Client::new())
.with(TracingMiddleware) .with(TracingMiddleware)
.build();` .build();
client.get("https://truelayer.com").send().await.unwrap(); 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 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. tracing subscriber to make use of the spans.

View file

@ -1,3 +1,7 @@
//! Opentracing middleware implementation for [`reqwest-middleware`].
//!
//! Attach [`TracingMiddleware`] to your client to automatically trace HTTP requests.
mod middleware; mod middleware;
#[cfg(any( #[cfg(any(
feature = "opentelemetry_0_13", feature = "opentelemetry_0_13",