burger/
steer.rs

1//! Given a collection of [services](Service) and a [`Picker`], the [`steer`] function constructs a
2//! [`Steer`] [`Service`].
3//!
4//! The [`Service::acquire`] on [`Steer`] acquires _all_ [permits](Service::Permit) from the
5//! collection, the [`Picker`] then selects which permit and [`Service`] to [`Service::call`].
6//!
7//! # Example
8//!
9//! ```rust
10//! use burger::*;
11//!
12//! # #[tokio::main]
13//! async fn main() {
14//! struct AlwaysFirst;
15//!
16//! impl<S, Request> steer::Picker<S, Request> for AlwaysFirst {
17//!     fn pick(&self, services: &[S], _request: &Request) -> usize {
18//!         0
19//!     }
20//! }
21//!
22//! let svcs = (0..10).map(|index| service_fn(move |x| async move { index * x }));
23//! let picker = AlwaysFirst;
24//! let svc = steer(svcs, picker);
25//! let response = svc.oneshot(7).await;
26//! assert_eq!(0, response);
27//! # }
28//! ```
29//!
30//! # Load
31//!
32//! This has _no_ [`Load`](crate::load::Load) implementation.
33
34use std::fmt;
35
36use futures_util::future::join_all;
37
38use crate::Service;
39
40/// A wrapper [`Service`] for the [`steer`] constructor.
41///
42/// See the [module](mod@crate::steer) for more information.
43#[derive(Debug)]
44pub struct Steer<S, P> {
45    services: Box<[S]>,
46    picker: P,
47}
48
49/// Picks a service from an underlying collection of services to steer requests.
50pub trait Picker<S, Request> {
51    /// Returns the index of the picked service.
52    ///
53    /// The returned index MUST be valid.
54    fn pick(&self, services: &[S], request: &Request) -> usize;
55}
56
57/// The [`Service::Permit`] type for [`Steer`].
58pub struct SteerPermit<'a, S, P, Request>
59where
60    S: Service<Request>,
61{
62    services: &'a [S],
63    permits: Vec<S::Permit<'a>>,
64    picker: &'a P,
65}
66
67impl<'a, S, P, Request> fmt::Debug for SteerPermit<'a, S, P, Request>
68where
69    S: Service<Request> + fmt::Debug,
70    S::Permit<'a>: fmt::Debug,
71    P: fmt::Debug,
72{
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.debug_struct("SteerPermit")
75            .field("services", &self.services)
76            .field("permits", &self.permits)
77            .field("picker", &self.picker)
78            .finish()
79    }
80}
81
82impl<Request, S, P> Service<Request> for Steer<S, P>
83where
84    S: Service<Request>,
85    P: Picker<S, Request>,
86{
87    type Response = S::Response;
88    type Permit<'a> = SteerPermit<'a, S, P, Request>
89    where
90        Self: 'a;
91
92    async fn acquire(&self) -> Self::Permit<'_> {
93        SteerPermit {
94            services: &self.services,
95            picker: &self.picker,
96            permits: join_all(self.services.iter().map(|x| x.acquire())).await,
97        }
98    }
99
100    async fn call(permit: Self::Permit<'_>, request: Request) -> Self::Response {
101        let SteerPermit {
102            services,
103            mut permits,
104            picker,
105        } = permit;
106        let index = picker.pick(services, &request);
107        let permit = permits.swap_remove(index);
108        drop(permits);
109        S::call(permit, request).await
110    }
111}
112
113/// Constructs a [`Service`] from a [collection](IntoIterator) of services whose [`Service::call`] is
114/// steered via a [`Picker`].
115///
116/// See [module](mod@crate::steer) for more information.
117pub fn steer<S, P>(services: impl IntoIterator<Item = S>, picker: P) -> Steer<S, P> {
118    Steer {
119        services: services.into_iter().collect(),
120        picker,
121    }
122}