Skip to content

Commit d562e10

Browse files
committed
feat(pool): add a Map pool service type
1 parent 11159bb commit d562e10

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

src/client/pool/map.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
//! Map pool utilities
2+
//!
3+
//! The map isn't a typical `Service`, but rather stand-alone type that can map
4+
//! requests to a key and service factory. This is because the service is more
5+
//! of a router, and cannot determine which inner service to check for
6+
//! backpressure since it's not know until the request is made.
7+
//!
8+
//! The map implementation allows customization of extracting a key, and how to
9+
//! construct a MakeService for that key.
10+
//!
11+
//! # Example
12+
//!
13+
//! ```rust,ignore
14+
//! # async fn run() {
15+
//! # use hyper_util::client::pool;
16+
//! # let req = http::Request::new(());
17+
//! # let some_http1_connector = || {
18+
//! # tower::service::service_fn(|_req| async { Ok::<_, &'static str>(()) })
19+
//! # };
20+
//! let mut map = pool::map::Map::builder()
21+
//! .keys(|uri| (uri.scheme().clone(), uri.authority().clone()))
22+
//! .values(|_| {
23+
//! some_http1_connector()
24+
//! })
25+
//! .build();
26+
//!
27+
//! let resp = map.service(req.uri()).call(req).await;
28+
//! # }
29+
//! ```
30+
31+
use std::collections::HashMap;
32+
33+
// expose the documentation
34+
#[cfg(docsrs)]
35+
pub use self::builder::Builder;
36+
37+
/// A map caching `MakeService`s per key.
38+
///
39+
/// Create one with the [`Map::builder()`].
40+
pub struct Map<T, Req>
41+
where
42+
T: target::Target<Req>,
43+
{
44+
map: HashMap<T::Key, T::Service>,
45+
targeter: T,
46+
}
47+
48+
// impl Map
49+
50+
impl Map<builder::StartHere, builder::StartHere> {
51+
/// Create a [`Builder`] to configure a new `Map`.
52+
pub fn builder<Dst>() -> builder::Builder<Dst, builder::WantsKeyer, builder::WantsServiceMaker>
53+
{
54+
builder::Builder::new()
55+
}
56+
}
57+
58+
impl<T, Req> Map<T, Req>
59+
where
60+
T: target::Target<Req>,
61+
{
62+
fn new(targeter: T) -> Self {
63+
Map {
64+
map: HashMap::new(),
65+
targeter,
66+
}
67+
}
68+
}
69+
70+
impl<T, Req> Map<T, Req>
71+
where
72+
T: target::Target<Req>,
73+
T::Key: Eq + std::hash::Hash,
74+
{
75+
/// Get a service after extracting the key from `req`.
76+
pub fn service(&mut self, req: &Req) -> &mut T::Service {
77+
let key = self.targeter.key(req);
78+
self.map
79+
.entry(key)
80+
.or_insert_with(|| self.targeter.service(req))
81+
}
82+
}
83+
84+
// sealed and unnameable for now
85+
mod target {
86+
pub trait Target<Dst> {
87+
type Key;
88+
type Service;
89+
90+
fn key(&self, dst: &Dst) -> Self::Key;
91+
fn service(&self, dst: &Dst) -> Self::Service;
92+
}
93+
}
94+
95+
// sealed and unnameable for now
96+
mod builder {
97+
use std::marker::PhantomData;
98+
99+
/// A builder to configure a `Map`.
100+
///
101+
/// # Unnameable
102+
///
103+
/// This type is normally unnameable, forbidding naming of the type within
104+
/// code. The type is exposed in the documentation to show which methods
105+
/// can be publicly called.
106+
pub struct Builder<Dst, K, S> {
107+
_dst: PhantomData<fn(Dst)>,
108+
keys: K,
109+
svcs: S,
110+
}
111+
112+
pub struct WantsKeyer;
113+
pub struct WantsServiceMaker;
114+
115+
pub enum StartHere {}
116+
117+
pub struct Built<K, S> {
118+
keys: K,
119+
svcs: S,
120+
}
121+
122+
impl<Dst> Builder<Dst, WantsKeyer, WantsServiceMaker> {
123+
pub(super) fn new() -> Self {
124+
Builder {
125+
_dst: PhantomData,
126+
keys: WantsKeyer,
127+
svcs: WantsServiceMaker,
128+
}
129+
}
130+
}
131+
132+
impl<Dst, S> Builder<Dst, WantsKeyer, S> {
133+
/// Provide a closure that extracts a pool key for the destination.
134+
pub fn keys<K, KK>(self, keyer: K) -> Builder<Dst, K, S>
135+
where
136+
K: Fn(&Dst) -> KK,
137+
{
138+
Builder {
139+
_dst: PhantomData,
140+
keys: keyer,
141+
svcs: self.svcs,
142+
}
143+
}
144+
}
145+
146+
impl<Dst, K> Builder<Dst, K, WantsServiceMaker> {
147+
/// Provide a closure to create a new `MakeService` for the destination.
148+
pub fn values<S, SS>(self, svcs: S) -> Builder<Dst, K, S>
149+
where
150+
S: Fn(&Dst) -> SS,
151+
{
152+
Builder {
153+
_dst: PhantomData,
154+
keys: self.keys,
155+
svcs,
156+
}
157+
}
158+
}
159+
160+
impl<Dst, K, S> Builder<Dst, K, S>
161+
where
162+
Built<K, S>: super::target::Target<Dst>,
163+
<Built<K, S> as super::target::Target<Dst>>::Key: Eq + std::hash::Hash,
164+
{
165+
/// Build the `Map` pool.
166+
pub fn build(self) -> super::Map<Built<K, S>, Dst> {
167+
super::Map::new(Built {
168+
keys: self.keys,
169+
svcs: self.svcs,
170+
})
171+
}
172+
}
173+
174+
impl super::target::Target<StartHere> for StartHere {
175+
type Key = StartHere;
176+
type Service = StartHere;
177+
178+
fn key(&self, _: &StartHere) -> Self::Key {
179+
match *self {}
180+
}
181+
182+
fn service(&self, _: &StartHere) -> Self::Service {
183+
match *self {}
184+
}
185+
}
186+
187+
impl<K, KK, S, SS, Dst> super::target::Target<Dst> for Built<K, S>
188+
where
189+
K: Fn(&Dst) -> KK,
190+
S: Fn(&Dst) -> SS,
191+
KK: Eq + std::hash::Hash,
192+
{
193+
type Key = KK;
194+
type Service = SS;
195+
196+
fn key(&self, dst: &Dst) -> Self::Key {
197+
(self.keys)(dst)
198+
}
199+
200+
fn service(&self, dst: &Dst) -> Self::Service {
201+
(self.svcs)(dst)
202+
}
203+
}
204+
}
205+
206+
#[cfg(test)]
207+
mod tests {
208+
#[test]
209+
fn smoke() {
210+
let mut pool = super::Map::builder().keys(|_| "a").values(|_| "b").build();
211+
pool.service(&"hello");
212+
}
213+
}

src/client/pool/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Composable pool services
22
33
pub mod cache;
4+
pub mod map;
45
pub mod negotiate;
56
pub mod singleton;

0 commit comments

Comments
 (0)