Skip to content

Commit a9883e6

Browse files
committed
feat(pool): add a Map pool service type
1 parent e644e08 commit a9883e6

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed

src/client/pool/map.rs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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+
/// Retains only the services specified by the predicate.
84+
pub fn retain<F>(&mut self, predicate: F)
85+
where
86+
F: FnMut(&T::Key, &mut T::Service) -> bool,
87+
{
88+
self.map.retain(predicate);
89+
}
90+
91+
/// Clears the map, removing all key-value pairs.
92+
pub fn clear(&mut self) {
93+
self.map.clear();
94+
}
95+
}
96+
97+
// sealed and unnameable for now
98+
mod target {
99+
pub trait Target<Dst> {
100+
type Key;
101+
type Service;
102+
103+
fn key(&self, dst: &Dst) -> Self::Key;
104+
fn service(&self, dst: &Dst) -> Self::Service;
105+
}
106+
}
107+
108+
// sealed and unnameable for now
109+
mod builder {
110+
use std::marker::PhantomData;
111+
112+
/// A builder to configure a `Map`.
113+
///
114+
/// # Unnameable
115+
///
116+
/// This type is normally unnameable, forbidding naming of the type within
117+
/// code. The type is exposed in the documentation to show which methods
118+
/// can be publicly called.
119+
pub struct Builder<Dst, K, S> {
120+
_dst: PhantomData<fn(Dst)>,
121+
keys: K,
122+
svcs: S,
123+
}
124+
125+
pub struct WantsKeyer;
126+
pub struct WantsServiceMaker;
127+
128+
pub enum StartHere {}
129+
130+
pub struct Built<K, S> {
131+
keys: K,
132+
svcs: S,
133+
}
134+
135+
impl<Dst> Builder<Dst, WantsKeyer, WantsServiceMaker> {
136+
pub(super) fn new() -> Self {
137+
Builder {
138+
_dst: PhantomData,
139+
keys: WantsKeyer,
140+
svcs: WantsServiceMaker,
141+
}
142+
}
143+
}
144+
145+
impl<Dst, S> Builder<Dst, WantsKeyer, S> {
146+
/// Provide a closure that extracts a pool key for the destination.
147+
pub fn keys<K, KK>(self, keyer: K) -> Builder<Dst, K, S>
148+
where
149+
K: Fn(&Dst) -> KK,
150+
{
151+
Builder {
152+
_dst: PhantomData,
153+
keys: keyer,
154+
svcs: self.svcs,
155+
}
156+
}
157+
}
158+
159+
impl<Dst, K> Builder<Dst, K, WantsServiceMaker> {
160+
/// Provide a closure to create a new `MakeService` for the destination.
161+
pub fn values<S, SS>(self, svcs: S) -> Builder<Dst, K, S>
162+
where
163+
S: Fn(&Dst) -> SS,
164+
{
165+
Builder {
166+
_dst: PhantomData,
167+
keys: self.keys,
168+
svcs,
169+
}
170+
}
171+
}
172+
173+
impl<Dst, K, S> Builder<Dst, K, S>
174+
where
175+
Built<K, S>: super::target::Target<Dst>,
176+
<Built<K, S> as super::target::Target<Dst>>::Key: Eq + std::hash::Hash,
177+
{
178+
/// Build the `Map` pool.
179+
pub fn build(self) -> super::Map<Built<K, S>, Dst> {
180+
super::Map::new(Built {
181+
keys: self.keys,
182+
svcs: self.svcs,
183+
})
184+
}
185+
}
186+
187+
impl super::target::Target<StartHere> for StartHere {
188+
type Key = StartHere;
189+
type Service = StartHere;
190+
191+
fn key(&self, _: &StartHere) -> Self::Key {
192+
match *self {}
193+
}
194+
195+
fn service(&self, _: &StartHere) -> Self::Service {
196+
match *self {}
197+
}
198+
}
199+
200+
impl<K, KK, S, SS, Dst> super::target::Target<Dst> for Built<K, S>
201+
where
202+
K: Fn(&Dst) -> KK,
203+
S: Fn(&Dst) -> SS,
204+
KK: Eq + std::hash::Hash,
205+
{
206+
type Key = KK;
207+
type Service = SS;
208+
209+
fn key(&self, dst: &Dst) -> Self::Key {
210+
(self.keys)(dst)
211+
}
212+
213+
fn service(&self, dst: &Dst) -> Self::Service {
214+
(self.svcs)(dst)
215+
}
216+
}
217+
}
218+
219+
#[cfg(test)]
220+
mod tests {
221+
#[test]
222+
fn smoke() {
223+
let mut pool = super::Map::builder().keys(|_| "a").values(|_| "b").build();
224+
pool.service(&"hello");
225+
}
226+
}

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)