Skip to main content

better_fetch/tower/
stack.rs

1//! Helpers for building transport stacks with [`ServiceBuilder`](tower::ServiceBuilder).
2
3use crate::backend::{HttpRequest, HttpResponse};
4use crate::Error;
5
6use super::{BoxHttpService, ReqwestHttpService};
7
8pub use tower::limit::{ConcurrencyLimitLayer, RateLimitLayer};
9pub use tower::timeout::TimeoutLayer;
10pub use tower::ServiceBuilder;
11
12/// Creates a reqwest-backed inner service for layer stacking.
13pub fn reqwest_service(client: reqwest::Client) -> ReqwestHttpService {
14    ReqwestHttpService::new(client)
15}
16
17/// Builds a type-erased transport stack from a reqwest client and a configuration closure.
18///
19/// Pass the result to [`ClientBuilder::http_service_boxed`](crate::ClientBuilder::http_service_boxed)
20/// or use [`ClientBuilder::transport_stack`](crate::ClientBuilder::transport_stack).
21pub fn build<F>(client: reqwest::Client, configure: F) -> BoxHttpService
22where
23    F: FnOnce(ReqwestHttpService) -> BoxHttpService,
24{
25    configure(ReqwestHttpService::new(client))
26}
27
28/// Extension trait to box a configured service stack.
29pub trait IntoBoxHttpService: Sized {
30    /// Boxes `self` as [`BoxHttpService`].
31    fn into_box(self) -> BoxHttpService;
32}
33
34impl<S> IntoBoxHttpService for S
35where
36    S: tower::Service<HttpRequest, Response = HttpResponse, Error = Error> + Clone + Send + 'static,
37    S::Future: Send + 'static,
38{
39    fn into_box(self) -> BoxHttpService {
40        BoxHttpService::new(self)
41    }
42}
43
44/// Convenience: concurrency limit on the transport stack.
45pub fn with_concurrency_limit(client: reqwest::Client, max_in_flight: usize) -> BoxHttpService {
46    build(client, |inner| {
47        ServiceBuilder::new()
48            .layer(ConcurrencyLimitLayer::new(max_in_flight))
49            .service(inner)
50            .into_box()
51    })
52}
53
54/// Wraps the reqwest inner service with [`tower::buffer::Buffer`](https://docs.rs/tower/latest/tower/buffer/struct.Buffer.html).
55///
56/// Use when the inner service is not [`Clone`] or cloning it is expensive. `Buffer::new`
57/// spawns a worker on the Tokio runtime; lightweight `Buffer` clones enqueue work to that
58/// worker. This is optional for typical reqwest-backed stacks — [`ServiceBackend`](crate::tower::ServiceBackend)
59/// already clones the boxed stack per request.
60pub fn with_buffer(client: reqwest::Client, capacity: usize) -> BoxHttpService {
61    build(client, |inner| {
62        let buffered = tower::buffer::Buffer::new(inner, capacity);
63        ServiceBuilder::new()
64            .map_err(|e: tower::BoxError| Error::transport_message(e.to_string()))
65            .service(buffered)
66            .into_box()
67    })
68}
69
70/// Logs each transport call at `DEBUG` (wire-level; complements [`LoggerPlugin`](crate::LoggerPlugin)).
71pub fn with_request_logging(client: reqwest::Client) -> BoxHttpService {
72    build(client, |inner| {
73        ServiceBuilder::new()
74            .map_request(|req: HttpRequest| {
75                tracing::debug!(method = %req.method, url = %req.url, "better-fetch transport");
76                req
77            })
78            .service(inner)
79            .into_box()
80    })
81}