Skip to content

Commit 7e77f7f

Browse files
authored
Merge pull request #253 from hit-box/multi-config
Multi-config CacheFuture routing
2 parents ab1ced4 + d08d2ad commit 7e77f7f

33 files changed

Lines changed: 1757 additions & 289 deletions

File tree

hitbox-configuration/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Added
99
- Initial release
10+
- `Endpoint` now implements `CacheConfigs` for use with `SelectiveCacheFuture` ([#253](https://github.com/hit-box/hitbox/pull/253))
1011

1112
### Changed
13+
- `Endpoint` now stores `policy` as `Arc<PolicyConfig>` ([#253](https://github.com/hit-box/hitbox/pull/253))
1214
- Adapted to hitbox-http 0.3 Config-based extractor/predicate API, added `Transform::Truncate` ([#202](https://github.com/hit-box/hitbox/pull/202))
15+
16+
### Fixed
17+
- README doctest missing type annotations for `Endpoint::builder()` ([#253](https://github.com/hit-box/hitbox/pull/253))

hitbox-configuration/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ serde_json = { workspace = true }
2727
serde-saphyr = { workspace = true }
2828
indexmap = { version = "2.10", features = ["serde"] }
2929
thiserror = { workspace = true }
30+
humantime-serde = "1"
3031
regex = { workspace = true }
3132
bytes = { workspace = true }
3233
bytesize = { version = "2", features = ["serde"] }

hitbox-configuration/src/config.rs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::{fmt::Debug, sync::Arc};
1+
use std::{fmt::Debug, num::NonZeroU8, sync::Arc, time::Duration};
22

3-
use hitbox::policy::PolicyConfig;
3+
use hitbox::policy;
44
use hitbox_http::{
55
extractors::{MethodConfig, NeutralExtractor, method::MethodExtractor, path::PathExtractor},
66
predicates::{
@@ -19,6 +19,89 @@ use crate::{
1919
types::MaybeUndefined,
2020
};
2121

22+
// =============================================================================
23+
// Serde-enabled policy types for configuration parsing
24+
// =============================================================================
25+
26+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Default)]
27+
pub enum StalePolicy {
28+
#[default]
29+
Return,
30+
Revalidate,
31+
OffloadRevalidate,
32+
}
33+
34+
impl From<StalePolicy> for policy::StalePolicy {
35+
fn from(s: StalePolicy) -> Self {
36+
match s {
37+
StalePolicy::Return => policy::StalePolicy::Return,
38+
StalePolicy::Revalidate => policy::StalePolicy::Revalidate,
39+
StalePolicy::OffloadRevalidate => policy::StalePolicy::OffloadRevalidate,
40+
}
41+
}
42+
}
43+
44+
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Default)]
45+
pub struct CacheBehaviorPolicy {
46+
#[serde(default)]
47+
stale: StalePolicy,
48+
}
49+
50+
impl From<CacheBehaviorPolicy> for policy::CacheBehaviorPolicy {
51+
fn from(s: CacheBehaviorPolicy) -> Self {
52+
policy::CacheBehaviorPolicy {
53+
stale: s.stale.into(),
54+
}
55+
}
56+
}
57+
58+
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
59+
pub struct EnabledCacheConfig {
60+
#[serde(default, with = "humantime_serde")]
61+
ttl: Option<Duration>,
62+
#[serde(default, with = "humantime_serde")]
63+
stale: Option<Duration>,
64+
#[serde(default)]
65+
policy: CacheBehaviorPolicy,
66+
concurrency: Option<NonZeroU8>,
67+
}
68+
69+
impl From<EnabledCacheConfig> for policy::EnabledCacheConfig {
70+
fn from(s: EnabledCacheConfig) -> Self {
71+
policy::EnabledCacheConfig {
72+
ttl: s.ttl,
73+
stale: s.stale,
74+
policy: s.policy.into(),
75+
concurrency: s.concurrency,
76+
}
77+
}
78+
}
79+
80+
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
81+
pub enum PolicyConfig {
82+
Enabled(EnabledCacheConfig),
83+
Disabled,
84+
}
85+
86+
impl Default for PolicyConfig {
87+
fn default() -> Self {
88+
PolicyConfig::Enabled(EnabledCacheConfig::default())
89+
}
90+
}
91+
92+
impl From<PolicyConfig> for policy::PolicyConfig {
93+
fn from(s: PolicyConfig) -> Self {
94+
match s {
95+
PolicyConfig::Enabled(config) => policy::PolicyConfig::Enabled(config.into()),
96+
PolicyConfig::Disabled => policy::PolicyConfig::Disabled,
97+
}
98+
}
99+
}
100+
101+
// =============================================================================
102+
// ConfigEndpoint
103+
// =============================================================================
104+
22105
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)]
23106
pub struct ConfigEndpoint {
24107
#[serde(default)]
@@ -84,7 +167,7 @@ impl ConfigEndpoint {
84167
extractors,
85168
request_predicates,
86169
response_predicates,
87-
policy: self.policy,
170+
policy: Arc::new(self.policy.into()),
88171
})
89172
}
90173
}

hitbox-configuration/src/endpoint.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{fmt::Debug, sync::Arc};
22

33
use hitbox::{
44
Extractor, Predicate,
5-
config::{BoxExtractor, BoxPredicate, CacheConfig},
5+
config::{BoxExtractor, BoxPredicate, CacheConfig, CacheConfigs},
66
policy::PolicyConfig,
77
};
88
use hitbox_http::{CacheableHttpRequest, CacheableHttpResponse};
@@ -28,7 +28,7 @@ where
2828
pub request_predicates: ArcRequestPredicate<ReqBody>,
2929
pub response_predicates: ArcResponsePredicate<ResBody>,
3030
pub extractors: ArcRequestExtractor<ReqBody>,
31-
pub policy: PolicyConfig,
31+
pub policy: Arc<PolicyConfig>,
3232
}
3333

3434
impl<ReqBody, ResBody> Debug for Endpoint<ReqBody, ResBody>
@@ -56,7 +56,7 @@ where
5656
request_predicates: Arc::clone(&self.request_predicates),
5757
response_predicates: Arc::clone(&self.response_predicates),
5858
extractors: Arc::clone(&self.extractors),
59-
policy: self.policy.clone(),
59+
policy: Arc::clone(&self.policy),
6060
}
6161
}
6262
}
@@ -103,8 +103,25 @@ where
103103
Arc::clone(&self.extractors)
104104
}
105105

106-
fn policy(&self) -> &PolicyConfig {
107-
&self.policy
106+
fn policy(&self) -> Arc<PolicyConfig> {
107+
Arc::clone(&self.policy)
108+
}
109+
}
110+
111+
impl<ReqBody, ResBody> CacheConfigs<CacheableHttpRequest<ReqBody>, CacheableHttpResponse<ResBody>>
112+
for Endpoint<ReqBody, ResBody>
113+
where
114+
ReqBody: hyper::body::Body + Send + 'static,
115+
ReqBody::Error: Send,
116+
ReqBody::Data: Send,
117+
ResBody: hyper::body::Body + Send + 'static,
118+
ResBody::Error: Send,
119+
ResBody::Data: Send,
120+
{
121+
type Config = Self;
122+
123+
fn configs(&self) -> &[Self::Config] {
124+
std::slice::from_ref(self)
108125
}
109126
}
110127

@@ -128,7 +145,7 @@ where
128145
request_predicates: Option<ArcRequestPredicate<ReqBody>>,
129146
response_predicates: Option<ArcResponsePredicate<ResBody>>,
130147
extractors: Option<ArcRequestExtractor<ReqBody>>,
131-
policy: PolicyConfig,
148+
policy: Arc<PolicyConfig>,
132149
}
133150

134151
impl<ReqBody, ResBody> EndpointBuilder<ReqBody, ResBody>
@@ -142,7 +159,7 @@ where
142159
request_predicates: None,
143160
response_predicates: None,
144161
extractors: None,
145-
policy: PolicyConfig::default(),
162+
policy: Arc::new(PolicyConfig::default()),
146163
}
147164
}
148165

@@ -181,7 +198,10 @@ where
181198

182199
/// Set the cache policy.
183200
pub fn policy(self, policy: PolicyConfig) -> Self {
184-
Self { policy, ..self }
201+
Self {
202+
policy: Arc::new(policy),
203+
..self
204+
}
185205
}
186206

187207
/// Build the Endpoint, using defaults for any unset fields.

hitbox-configuration/tests/test_config_endpoint.rs

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
use bytes::Bytes;
2-
use hitbox::policy::PolicyConfig;
32
use hitbox_configuration::{ConfigEndpoint, types::MaybeUndefined};
43
use http_body_util::Empty;
5-
use std::time::Duration;
64

75
type ReqBody = Empty<Bytes>;
86
type ResBody = Empty<Bytes>;
97

10-
fn default_policy() -> PolicyConfig {
11-
PolicyConfig::builder().ttl(Duration::from_secs(60)).build()
12-
}
13-
148
#[test]
159
fn test_extractors_variants() {
1610
for extractors in [
@@ -20,9 +14,7 @@ fn test_extractors_variants() {
2014
] {
2115
let endpoint = ConfigEndpoint {
2216
extractors,
23-
request: MaybeUndefined::Undefined,
24-
response: MaybeUndefined::Undefined,
25-
policy: default_policy(),
17+
..Default::default()
2618
};
2719
assert!(endpoint.extractors::<ReqBody>().is_ok());
2820
}
@@ -31,26 +23,17 @@ fn test_extractors_variants() {
3123
#[test]
3224
fn test_into_endpoint_variants() {
3325
// all undefined
34-
let endpoint = ConfigEndpoint {
35-
extractors: MaybeUndefined::Undefined,
36-
request: MaybeUndefined::Undefined,
37-
response: MaybeUndefined::Undefined,
38-
policy: default_policy(),
39-
};
26+
let endpoint = ConfigEndpoint::default();
4027
assert!(endpoint.into_endpoint::<ReqBody, ResBody>().is_ok());
4128

4229
// all null
4330
let endpoint = ConfigEndpoint {
4431
extractors: MaybeUndefined::Null,
4532
request: MaybeUndefined::Null,
4633
response: MaybeUndefined::Null,
47-
policy: default_policy(),
34+
..Default::default()
4835
};
4936
assert!(endpoint.into_endpoint::<ReqBody, ResBody>().is_ok());
50-
51-
// default
52-
let endpoint = ConfigEndpoint::default();
53-
assert!(endpoint.into_endpoint::<ReqBody, ResBody>().is_ok());
5437
}
5538

5639
#[test]

hitbox-core/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- `CacheConfig` and `CacheConfigs` traits for cache configuration abstraction ([#253](https://github.com/hit-box/hitbox/pull/253))
10+
11+
### Changed
12+
- `PolicyConfig` and related policy types moved from `hitbox` crate ([#253](https://github.com/hit-box/hitbox/pull/253))
13+
- **Breaking:** `CacheConfig::policy()` now returns `Arc<PolicyConfig>` instead of `&PolicyConfig` for consistency with other trait methods ([#253](https://github.com/hit-box/hitbox/pull/253))
814

915
### Changed
1016
- **Breaking:** `Upstream::call` now takes `self` by value instead of `&mut self` — the FSM calls upstream exactly once, so consuming is semantically correct and simplifies lifetime handling ([#206](https://github.com/hit-box/hitbox/pull/206))

hitbox-core/src/config.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Cache configuration traits.
2+
//!
3+
//! - [`CacheConfig`] — single cache configuration (predicates, extractor, policy)
4+
//! - [`CacheConfigs`] — ordered collection of configurations for multi-config routing
5+
6+
use std::sync::Arc;
7+
8+
use crate::Extractor;
9+
use crate::policy::PolicyConfig;
10+
use crate::predicate::Predicate;
11+
12+
/// Trait for cache configuration.
13+
///
14+
/// Provides predicates for determining cacheability, extractors for generating
15+
/// cache keys, and policy for TTL/staleness behavior.
16+
pub trait CacheConfig<Req, Res> {
17+
/// Predicate type for filtering requests.
18+
type RequestPredicate: Predicate<Subject = Req> + Send + Sync + 'static;
19+
/// Predicate type for filtering responses.
20+
type ResponsePredicate: Predicate<Subject = Res> + Send + Sync + 'static;
21+
/// Extractor type for generating cache keys.
22+
type Extractor: Extractor<Subject = Req> + Send + Sync + 'static;
23+
24+
/// Returns predicates that decide if a request should be cached.
25+
fn request_predicates(&self) -> Self::RequestPredicate;
26+
/// Returns predicates that decide if a response should be cached.
27+
fn response_predicates(&self) -> Self::ResponsePredicate;
28+
/// Returns extractors that generate cache keys from requests.
29+
fn extractors(&self) -> Self::Extractor;
30+
/// Returns TTL and behavior policy for cached entries.
31+
fn policy(&self) -> Arc<PolicyConfig>;
32+
}
33+
34+
/// Trait for providing one or more cache configurations.
35+
///
36+
/// Configurations are evaluated in order; first match wins.
37+
/// Use [`CacheConfig`] for a single configuration that wraps itself,
38+
/// or a multi-config container like `SelectiveConfig` for routing.
39+
pub trait CacheConfigs<Req, Res> {
40+
/// The individual config type.
41+
type Config: CacheConfig<Req, Res>;
42+
43+
/// Returns the ordered list of configurations.
44+
fn configs(&self) -> &[Self::Config];
45+
}
46+
47+
impl<T, Req, Res> CacheConfig<Req, Res> for Arc<T>
48+
where
49+
T: CacheConfig<Req, Res>,
50+
{
51+
type RequestPredicate = T::RequestPredicate;
52+
type ResponsePredicate = T::ResponsePredicate;
53+
type Extractor = T::Extractor;
54+
55+
fn request_predicates(&self) -> Self::RequestPredicate {
56+
self.as_ref().request_predicates()
57+
}
58+
59+
fn response_predicates(&self) -> Self::ResponsePredicate {
60+
self.as_ref().response_predicates()
61+
}
62+
63+
fn extractors(&self) -> Self::Extractor {
64+
self.as_ref().extractors()
65+
}
66+
67+
fn policy(&self) -> Arc<PolicyConfig> {
68+
self.as_ref().policy()
69+
}
70+
}

hitbox-core/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![warn(missing_docs)]
33

44
pub mod cacheable;
5+
pub mod config;
56
pub mod context;
67
pub mod extractor;
78
pub mod key;
@@ -15,6 +16,7 @@ pub mod upstream;
1516
pub mod value;
1617

1718
pub use cacheable::Cacheable;
19+
pub use config::{CacheConfig, CacheConfigs};
1820
pub use context::{
1921
BoxContext, CacheContext, CacheStatus, CacheStatusExt, Context, ReadMode, ResponseSource,
2022
finalize_context,
@@ -23,7 +25,10 @@ pub use extractor::Extractor;
2325
pub use key::{CacheKey, KeyPart, KeyParts};
2426
pub use label::BackendLabel;
2527
pub use offload::{DisabledOffload, Offload, OffloadKey};
26-
pub use policy::{CachePolicy, EntityPolicyConfig};
28+
pub use policy::{
29+
CacheBehaviorPolicy, CachePolicy, ConcurrencyLimit, EnabledCacheConfig, EntityPolicyConfig,
30+
PolicyConfig, PolicyConfigBuilder, StalePolicy,
31+
};
2732
pub use predicate::{And, Neutral, Not, Or, Predicate, PredicateExt, PredicateResult};
2833
pub use request::{CacheablePolicyData, CacheableRequest, RequestCachePolicy};
2934
pub use response::{CacheState, CacheableResponse, ResponseCachePolicy};

0 commit comments

Comments
 (0)