diff --git a/Cargo.toml b/Cargo.toml index 78801fbd..b53b45eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,3 +71,4 @@ criterion = "0.8" # SmallBox for inline trait objects smallbox = "0.8" + diff --git a/hitbox-configuration/tests/test_request.rs b/hitbox-configuration/tests/test_request.rs index 91282c93..04cdcc80 100644 --- a/hitbox-configuration/tests/test_request.rs +++ b/hitbox-configuration/tests/test_request.rs @@ -7,6 +7,7 @@ use hitbox_configuration::{ }, types::MaybeUndefined, }; +use hitbox_core::EvalContext; use hitbox_http::predicates::NeutralRequestPredicate; use hitbox_http::{BufferedBody, CacheableHttpRequest}; use http::Request as HttpRequest; @@ -57,7 +58,7 @@ async fn test_expression_into_predicates() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::NonCacheable(_))); } @@ -132,7 +133,7 @@ async fn test_or_with_matching_first_predicate() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::Cacheable(_))); } @@ -154,7 +155,7 @@ async fn test_or_with_matching_middle_predicate() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::Cacheable(_))); } @@ -176,7 +177,7 @@ async fn test_or_with_matching_last_predicate() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::Cacheable(_))); } @@ -198,7 +199,7 @@ async fn test_or_with_no_matching_predicates() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::NonCacheable(_))); } @@ -216,7 +217,7 @@ async fn test_or_with_single_predicate_matching() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::Cacheable(_))); } @@ -234,7 +235,7 @@ async fn test_or_with_single_predicate_not_matching() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::NonCacheable(_))); } @@ -256,7 +257,7 @@ async fn test_or_with_mixed_predicate_types() { .body(BufferedBody::Passthrough(Empty::::new())) .unwrap(), ); - let cacheable = predicate_or.check(request).await; + let cacheable = predicate_or.check(request, &mut EvalContext::new()).await; assert!(matches!(cacheable, PredicateResult::Cacheable(_))); } diff --git a/hitbox-core/CHANGELOG.md b/hitbox-core/CHANGELOG.md index 2da735db..61c2710a 100644 --- a/hitbox-core/CHANGELOG.md +++ b/hitbox-core/CHANGELOG.md @@ -7,10 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - `CacheConfig` and `CacheConfigs` traits for cache configuration abstraction ([#253](https://github.com/hit-box/hitbox/pull/253)) +- `EvalContext` type-map for sharing computed values across predicates and extractors ### Changed - `PolicyConfig` and related policy types moved from `hitbox` crate ([#253](https://github.com/hit-box/hitbox/pull/253)) -- **Breaking:** `CacheConfig::policy()` now returns `Arc` instead of `&PolicyConfig` for consistency with other trait methods ([#253](https://github.com/hit-box/hitbox/pull/253)) +- **Breaking:** `CacheConfig::policy()` now returns `Arc` instead of `&PolicyConfig` ([#253](https://github.com/hit-box/hitbox/pull/253)) +- **Breaking:** `Predicate::check()` now accepts `&mut EvalContext` parameter +- **Breaking:** `Extractor::get()` now accepts `&mut EvalContext` parameter ### Changed - **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)) diff --git a/hitbox-core/Cargo.toml b/hitbox-core/Cargo.toml index d45d28f2..544c1377 100644 --- a/hitbox-core/Cargo.toml +++ b/hitbox-core/Cargo.toml @@ -21,7 +21,6 @@ bytes = { workspace = true } smol_str = { workspace = true } smallbox = { workspace = true } pin-project = { workspace = true } - # Optional rkyv support rkyv = { workspace = true, optional = true } diff --git a/hitbox-core/src/eval_context.rs b/hitbox-core/src/eval_context.rs new file mode 100644 index 00000000..4c17fb9a --- /dev/null +++ b/hitbox-core/src/eval_context.rs @@ -0,0 +1,172 @@ +//! Evaluation context for predicates and extractors. +//! +//! [`EvalContext`] is a type-map that allows predicates and extractors to share +//! computed values during a single evaluation phase. This avoids redundant +//! expensive operations (e.g., collecting a chunked body and deserializing it +//! into JSON) when multiple predicates or extractors need the same data. +//! +//! ## Usage +//! +//! ```ignore +//! use hitbox_core::EvalContext; +//! +//! struct ParsedBody(serde_json::Value); +//! +//! let mut ctx = EvalContext::new(); +//! +//! let body = ctx.get_or_insert_with(|| { +//! let collected = body_bytes; +//! ParsedBody(serde_json::from_slice(&collected).unwrap()) +//! }); +//! ``` +//! +//! ## Lifecycle +//! +//! An `EvalContext` is created inside each `cache_policy` implementation: +//! one for the request phase (shared by request predicates and extractors) +//! and another for the response phase (used by response predicates). + +use std::any::{Any, TypeId}; +use std::collections::HashMap; + +/// A type-map for sharing computed values across predicates and extractors. +/// +/// Each value is keyed by its concrete type (`TypeId`), so only one value +/// of each type can be stored. Use newtype wrappers to store multiple +/// values of the same underlying type. +/// +/// Predicates and extractors are evaluated sequentially, so no interior +/// mutability or synchronization is needed. Methods that read take `&self`, +/// methods that write take `&mut self`. +pub struct EvalContext { + map: HashMap>, +} + +impl EvalContext { + /// Creates an empty evaluation context. + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + /// Inserts a value into the context. + /// + /// If a value of this type already exists, it is replaced. + pub fn insert(&mut self, val: T) { + self.map.insert(TypeId::of::(), Box::new(val)); + } + + /// Returns a reference to a value of the given type, if present. + pub fn get(&self) -> Option<&T> { + self.map + .get(&TypeId::of::()) + .and_then(|boxed| boxed.downcast_ref()) + } + + /// Returns a reference to a value of the given type, inserting a default + /// computed by `f` if not present. + pub fn get_or_insert_with(&mut self, f: impl FnOnce() -> T) -> &T { + self.map + .entry(TypeId::of::()) + .or_insert_with(|| Box::new(f())) + .downcast_ref() + .expect("type mismatch in EvalContext (bug)") + } + + /// Returns `true` if the context contains a value of the given type. + pub fn contains(&self) -> bool { + self.map.contains_key(&TypeId::of::()) + } + + /// Removes a value of the given type, returning it if present. + pub fn remove(&mut self) -> Option { + self.map + .remove(&TypeId::of::()) + .and_then(|boxed| boxed.downcast().ok()) + .map(|boxed| *boxed) + } +} + +impl Default for EvalContext { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Debug for EvalContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EvalContext") + .field("entries", &self.map.len()) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct StringValue(String); + struct Counter(u32); + + #[test] + fn test_insert_and_get() { + let mut ctx = EvalContext::new(); + ctx.insert(StringValue("hello".into())); + + assert!(ctx.contains::()); + assert_eq!(ctx.get::().unwrap().0, "hello"); + } + + #[test] + fn test_insert_replaces() { + let mut ctx = EvalContext::new(); + ctx.insert(Counter(1)); + ctx.insert(Counter(2)); + assert_eq!(ctx.get::().unwrap().0, 2); + } + + #[test] + fn test_get_or_insert_with() { + let mut ctx = EvalContext::new(); + + // First call inserts + let val = ctx.get_or_insert_with(|| Counter(42)); + assert_eq!(val.0, 42); + + // Second call returns existing + let val = ctx.get_or_insert_with(|| Counter(99)); + assert_eq!(val.0, 42); + } + + #[test] + fn test_remove() { + let mut ctx = EvalContext::new(); + ctx.insert(Counter(10)); + let removed = ctx.remove::(); + assert_eq!(removed.unwrap().0, 10); + assert!(!ctx.contains::()); + } + + #[test] + fn test_missing_type_returns_none() { + let ctx = EvalContext::new(); + assert!(ctx.get::().is_none()); + } + + #[test] + fn test_multiple_types() { + let mut ctx = EvalContext::new(); + ctx.insert(StringValue("a".into())); + ctx.insert(Counter(1)); + + assert_eq!(ctx.get::().unwrap().0, "a"); + assert_eq!(ctx.get::().unwrap().0, 1); + } + + #[test] + fn test_default() { + let ctx = EvalContext::default(); + assert!(!ctx.contains::()); + } +} diff --git a/hitbox-core/src/extractor.rs b/hitbox-core/src/extractor.rs index d2c45081..6378c74d 100644 --- a/hitbox-core/src/extractor.rs +++ b/hitbox-core/src/extractor.rs @@ -39,6 +39,7 @@ use std::sync::Arc; use async_trait::async_trait; +use crate::EvalContext; use crate::KeyParts; /// Trait for extracting cache key components from a subject. @@ -73,7 +74,7 @@ pub trait Extractor { /// Extract cache key components from the subject. /// /// Returns a [`KeyParts`] containing the subject and accumulated key parts. - async fn get(&self, subject: Self::Subject) -> KeyParts; + async fn get(&self, subject: Self::Subject, ctx: &mut EvalContext) -> KeyParts; } #[async_trait] @@ -84,8 +85,8 @@ where { type Subject = T::Subject; - async fn get(&self, subject: T::Subject) -> KeyParts { - self.get(subject).await + async fn get(&self, subject: T::Subject, ctx: &mut EvalContext) -> KeyParts { + (**self).get(subject, ctx).await } } @@ -97,8 +98,8 @@ where { type Subject = T::Subject; - async fn get(&self, subject: T::Subject) -> KeyParts { - self.as_ref().get(subject).await + async fn get(&self, subject: T::Subject, ctx: &mut EvalContext) -> KeyParts { + self.as_ref().get(subject, ctx).await } } @@ -110,7 +111,7 @@ where { type Subject = T::Subject; - async fn get(&self, subject: T::Subject) -> KeyParts { - self.as_ref().get(subject).await + async fn get(&self, subject: T::Subject, ctx: &mut EvalContext) -> KeyParts { + self.as_ref().get(subject, ctx).await } } diff --git a/hitbox-core/src/lib.rs b/hitbox-core/src/lib.rs index 90465fc0..3b496327 100644 --- a/hitbox-core/src/lib.rs +++ b/hitbox-core/src/lib.rs @@ -4,6 +4,7 @@ pub mod cacheable; pub mod config; pub mod context; +pub mod eval_context; pub mod extractor; pub mod key; pub mod label; @@ -21,6 +22,7 @@ pub use context::{ BoxContext, CacheContext, CacheStatus, CacheStatusExt, Context, ReadMode, ResponseSource, finalize_context, }; +pub use eval_context::EvalContext; pub use extractor::Extractor; pub use key::{CacheKey, KeyPart, KeyParts}; pub use label::BackendLabel; diff --git a/hitbox-core/src/predicate/combinators.rs b/hitbox-core/src/predicate/combinators.rs index 339b694c..98b71c0a 100644 --- a/hitbox-core/src/predicate/combinators.rs +++ b/hitbox-core/src/predicate/combinators.rs @@ -18,6 +18,8 @@ use async_trait::async_trait; +use crate::EvalContext; + use super::{Predicate, PredicateResult}; /// Inverts a predicate result. @@ -44,8 +46,12 @@ where { type Subject = P::Subject; - async fn check(&self, subject: Self::Subject) -> PredicateResult { - match self.predicate.check(subject).await { + async fn check( + &self, + subject: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.predicate.check(subject, ctx).await { PredicateResult::Cacheable(s) => PredicateResult::NonCacheable(s), PredicateResult::NonCacheable(s) => PredicateResult::Cacheable(s), } @@ -78,9 +84,13 @@ where { type Subject = L::Subject; - async fn check(&self, subject: Self::Subject) -> PredicateResult { - match self.left.check(subject).await { - PredicateResult::Cacheable(s) => self.right.check(s).await, + async fn check( + &self, + subject: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.left.check(subject, ctx).await { + PredicateResult::Cacheable(s) => self.right.check(s, ctx).await, non_cacheable => non_cacheable, } } @@ -112,9 +122,13 @@ where { type Subject = L::Subject; - async fn check(&self, subject: Self::Subject) -> PredicateResult { - match self.left.check(subject).await { - PredicateResult::NonCacheable(s) => self.right.check(s).await, + async fn check( + &self, + subject: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.left.check(subject, ctx).await { + PredicateResult::NonCacheable(s) => self.right.check(s, ctx).await, cacheable => cacheable, } } diff --git a/hitbox-core/src/predicate/mod.rs b/hitbox-core/src/predicate/mod.rs index 6ff1d1bc..5e22f9c8 100644 --- a/hitbox-core/src/predicate/mod.rs +++ b/hitbox-core/src/predicate/mod.rs @@ -25,6 +25,8 @@ use std::sync::Arc; use async_trait::async_trait; +use crate::EvalContext; + pub use combinators::{And, Not, Or, PredicateExt}; pub use neutral::Neutral; @@ -93,7 +95,11 @@ pub trait Predicate { /// /// Returns [`PredicateResult::Cacheable`] if the subject should be cached, /// or [`PredicateResult::NonCacheable`] if it should bypass the cache. - async fn check(&self, subject: Self::Subject) -> PredicateResult; + async fn check( + &self, + subject: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult; } #[async_trait] @@ -104,8 +110,12 @@ where { type Subject = T::Subject; - async fn check(&self, subject: T::Subject) -> PredicateResult { - self.as_ref().check(subject).await + async fn check( + &self, + subject: T::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + self.as_ref().check(subject, ctx).await } } @@ -117,8 +127,12 @@ where { type Subject = T::Subject; - async fn check(&self, subject: T::Subject) -> PredicateResult { - (*self).check(subject).await + async fn check( + &self, + subject: T::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + (*self).check(subject, ctx).await } } @@ -130,8 +144,12 @@ where { type Subject = T::Subject; - async fn check(&self, subject: T::Subject) -> PredicateResult { - self.as_ref().check(subject).await + async fn check( + &self, + subject: T::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + self.as_ref().check(subject, ctx).await } } @@ -147,7 +165,8 @@ mod tests { // PredicateExt works on Box because Box is Sized let combined = p1.or(p2); - let result = combined.check(42).await; + let mut ctx = EvalContext::new(); + let result = combined.check(42, &mut ctx).await; assert!(matches!(result, PredicateResult::Cacheable(42))); } @@ -160,7 +179,8 @@ mod tests { // Chain: p1.and(p2).or(p3).not() let combined = p1.and(p2).or(p3).not(); - let result = combined.check(42).await; + let mut ctx = EvalContext::new(); + let result = combined.check(42, &mut ctx).await; // Neutral returns Cacheable, so: Cacheable AND Cacheable = Cacheable, OR Cacheable = Cacheable, NOT = NonCacheable assert!(matches!(result, PredicateResult::NonCacheable(42))); } @@ -174,7 +194,8 @@ mod tests { // Can chain after boxing let combined = p1.or(p2); - let result = combined.check(42).await; + let mut ctx = EvalContext::new(); + let result = combined.check(42, &mut ctx).await; assert!(matches!(result, PredicateResult::Cacheable(42))); } @@ -186,8 +207,9 @@ mod tests { Neutral::::new().not().boxed(), ]; - let result1 = predicates[0].check(1).await; - let result2 = predicates[1].check(2).await; + let mut ctx = EvalContext::new(); + let result1 = predicates[0].check(1, &mut ctx).await; + let result2 = predicates[1].check(2, &mut ctx).await; assert!(matches!(result1, PredicateResult::Cacheable(1))); assert!(matches!(result2, PredicateResult::NonCacheable(2))); diff --git a/hitbox-core/src/predicate/neutral.rs b/hitbox-core/src/predicate/neutral.rs index 6ec7e28c..d3803a7b 100644 --- a/hitbox-core/src/predicate/neutral.rs +++ b/hitbox-core/src/predicate/neutral.rs @@ -4,6 +4,8 @@ use std::marker::PhantomData; use async_trait::async_trait; +use crate::EvalContext; + use super::{Predicate, PredicateResult}; /// A predicate that always returns `Cacheable`. @@ -42,7 +44,11 @@ where { type Subject = S; - async fn check(&self, subject: Self::Subject) -> PredicateResult { + async fn check( + &self, + subject: Self::Subject, + _ctx: &mut EvalContext, + ) -> PredicateResult { PredicateResult::Cacheable(subject) } } diff --git a/hitbox-core/src/response.rs b/hitbox-core/src/response.rs index 104e9f58..b14eaf75 100644 --- a/hitbox-core/src/response.rs +++ b/hitbox-core/src/response.rs @@ -38,7 +38,7 @@ use chrono::Utc; use pin_project::pin_project; use crate::{ - CachePolicy, EntityPolicyConfig, + CachePolicy, EntityPolicyConfig, EvalContext, predicate::{Predicate, PredicateResult}, value::CacheValue, }; @@ -90,7 +90,7 @@ pub enum CacheState { /// # Example Implementation /// /// ``` -/// use hitbox_core::{CacheableResponse, CachePolicy, EntityPolicyConfig}; +/// use hitbox_core::{CacheableResponse, CachePolicy, EntityPolicyConfig, EvalContext}; /// use hitbox_core::predicate::{Predicate, PredicateResult}; /// use hitbox_core::response::ResponseCachePolicy; /// use hitbox_core::value::CacheValue; @@ -116,7 +116,8 @@ pub enum CacheState { /// where /// P: Predicate + Send + Sync, /// { -/// match predicates.check(self).await { +/// let mut ctx = EvalContext::new(); +/// match predicates.check(self, &mut ctx).await { /// PredicateResult::Cacheable(data) => { /// let cached = data.body.clone(); /// CachePolicy::Cacheable(CacheValue::new( @@ -208,7 +209,8 @@ macro_rules! impl_cacheable_response_for_scalar { where P: Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(data) => { let cached = data.clone(); CachePolicy::Cacheable(CacheValue::new( @@ -265,7 +267,8 @@ where where P: Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(data) => { let cached = data.clone(); CachePolicy::Cacheable(CacheValue::new( @@ -367,8 +370,9 @@ where where P: Predicate + Send + Sync, { + let mut ctx = EvalContext::new(); match self { - Ok(response) => match predicates.check(response).await { + Ok(response) => match predicates.check(response, &mut ctx).await { PredicateResult::Cacheable(cacheable) => match cacheable.into_cached().await { CachePolicy::Cacheable(res) => CachePolicy::Cacheable(CacheValue::new( res, diff --git a/hitbox-core/tests/response.rs b/hitbox-core/tests/response.rs index fd33586a..cfaf82bc 100644 --- a/hitbox-core/tests/response.rs +++ b/hitbox-core/tests/response.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; use chrono::Utc; use hitbox_core::{ - CachePolicy, CacheValue, CacheableResponse, EntityPolicyConfig, Predicate, PredicateResult, + CachePolicy, CacheValue, CacheableResponse, EntityPolicyConfig, EvalContext, Predicate, + PredicateResult, }; #[derive(Clone, Debug)] @@ -35,7 +36,8 @@ impl CacheableResponse for TestResponse { where P: hitbox_core::Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(cacheable) => match cacheable.into_cached().await { CachePolicy::Cacheable(res) => { CachePolicy::Cacheable(CacheValue::new(res, Some(Utc::now()), Some(Utc::now()))) @@ -68,7 +70,11 @@ impl NeuralPredicate { impl Predicate for NeuralPredicate { type Subject = TestResponse; - async fn check(&self, subject: Self::Subject) -> PredicateResult { + async fn check( + &self, + subject: Self::Subject, + _ctx: &mut EvalContext, + ) -> PredicateResult { PredicateResult::Cacheable(subject) } } diff --git a/hitbox-derive/CHANGELOG.md b/hitbox-derive/CHANGELOG.md index 59fd5310..34e5e12f 100644 --- a/hitbox-derive/CHANGELOG.md +++ b/hitbox-derive/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `#[cached]` codegen uses `CacheAccess` trait instead of concrete `Cache` type ([#206](https://github.com/hit-box/hitbox/pull/206)) - `#[derive(CacheableRequest)]` generates GAT-based impl to support non-`'static` request types ([#206](https://github.com/hit-box/hitbox/pull/206)) +- Generated `CacheableRequest` and `CacheableResponse` impls now pass `&mut EvalContext` ## [0.2.1] - 2026-02-17 diff --git a/hitbox-derive/src/cacheable_request/trait_impl.rs b/hitbox-derive/src/cacheable_request/trait_impl.rs index 048d712e..a73da50c 100644 --- a/hitbox-derive/src/cacheable_request/trait_impl.rs +++ b/hitbox-derive/src/cacheable_request/trait_impl.rs @@ -45,9 +45,10 @@ impl<'a> ToTokens for CacheableRequestImpl<'a> { __E: hitbox::Extractor + Send + Sync + '__a, { ::std::boxed::Box::pin(async move { - match predicates.check(self).await { + let mut ctx = hitbox::EvalContext::new(); + match predicates.check(self, &mut ctx).await { hitbox::predicate::PredicateResult::Cacheable(subject) => { - let (subject, key) = extractors.get(subject).await.into_cache_key(); + let (subject, key) = extractors.get(subject, &mut ctx).await.into_cache_key(); hitbox::CachePolicy::Cacheable( hitbox::CacheablePolicyData::new(key, subject) ) diff --git a/hitbox-derive/src/cacheable_response/trait_impl.rs b/hitbox-derive/src/cacheable_response/trait_impl.rs index efff5cca..08db46d6 100644 --- a/hitbox-derive/src/cacheable_response/trait_impl.rs +++ b/hitbox-derive/src/cacheable_response/trait_impl.rs @@ -49,7 +49,8 @@ impl<'a> CacheableResponseImpl<'a> { where __P: hitbox::predicate::Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = hitbox::EvalContext::new(); + match predicates.check(self, &mut ctx).await { hitbox::predicate::PredicateResult::Cacheable(data) => { let cached = data.clone(); hitbox::CachePolicy::Cacheable( @@ -104,7 +105,8 @@ impl<'a> CacheableResponseImpl<'a> { where __P: hitbox::predicate::Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = hitbox::EvalContext::new(); + match predicates.check(self, &mut ctx).await { hitbox::predicate::PredicateResult::Cacheable(data) => { let cached = #cached_name { #(#field_idents: data.#field_idents,)* diff --git a/hitbox-fn/CHANGELOG.md b/hitbox-fn/CHANGELOG.md index dba07f3c..68a39ac4 100644 --- a/hitbox-fn/CHANGELOG.md +++ b/hitbox-fn/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Relaxed `'static` bounds — `Args`, `FnExtractor`, and `FnUpstream` now support non-`'static` lifetimes ([#206](https://github.com/hit-box/hitbox/pull/206)) - Removed direct `hitbox-core` dependency in favor of `hitbox` re-exports ([#206](https://github.com/hit-box/hitbox/pull/206)) +- `Args` and `FnExtractor` now accept `&mut EvalContext` ## [0.2.1] - 2026-02-17 diff --git a/hitbox-fn/src/args.rs b/hitbox-fn/src/args.rs index b8551fae..6a1961cc 100644 --- a/hitbox-fn/src/args.rs +++ b/hitbox-fn/src/args.rs @@ -128,9 +128,10 @@ macro_rules! impl_cacheable_request_for_args { E: Extractor + Send + Sync + 'a, { Box::pin(async move { - match predicates.check(self).await { + let mut ctx = hitbox::EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(subject) => { - let (subject, key) = extractors.get(subject).await.into_cache_key(); + let (subject, key) = extractors.get(subject, &mut ctx).await.into_cache_key(); CachePolicy::Cacheable(hitbox::CacheablePolicyData::new(key, subject)) } PredicateResult::NonCacheable(subject) => CachePolicy::NonCacheable(subject), @@ -159,9 +160,10 @@ macro_rules! impl_cacheable_request_for_args { E: Extractor + Send + Sync + 'a, { Box::pin(async move { - match predicates.check(self).await { + let mut ctx = hitbox::EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(subject) => { - let (subject, key) = extractors.get(subject).await.into_cache_key(); + let (subject, key) = extractors.get(subject, &mut ctx).await.into_cache_key(); CachePolicy::Cacheable(hitbox::CacheablePolicyData::new(key, subject)) } PredicateResult::NonCacheable(subject) => CachePolicy::NonCacheable(subject), diff --git a/hitbox-fn/src/extractor.rs b/hitbox-fn/src/extractor.rs index ae59a4bb..e8506527 100644 --- a/hitbox-fn/src/extractor.rs +++ b/hitbox-fn/src/extractor.rs @@ -183,7 +183,11 @@ where { type Subject = T; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get( + &self, + subject: Self::Subject, + _ctx: &mut hitbox::EvalContext, + ) -> KeyParts { let extracted = subject.extract(); let mut parts = KeyParts::new(subject); parts.push(KeyPart::new("fn", Some(self.fn_path))); diff --git a/hitbox-fn/tests/cached_macro.rs b/hitbox-fn/tests/cached_macro.rs index 2300859f..03acc2b8 100644 --- a/hitbox-fn/tests/cached_macro.rs +++ b/hitbox-fn/tests/cached_macro.rs @@ -677,7 +677,10 @@ async fn test_zero_args_generated_key() { assert_eq!(*skipped.value(), "ctx"); let extractor = FnExtractor::>::new("no_args_function"); - let (_, key) = extractor.get(Args(())).await.into_cache_key(); + let (_, key) = extractor + .get(Args(()), &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); // Zero-arg function should produce key with only the function name assert_eq!(key.to_string(), "fn=no_args_function"); diff --git a/hitbox-fn/tests/key_extract.rs b/hitbox-fn/tests/key_extract.rs index f8f8f490..40b80701 100644 --- a/hitbox-fn/tests/key_extract.rs +++ b/hitbox-fn/tests/key_extract.rs @@ -126,7 +126,7 @@ async fn test_extractor_with_derived_type() { let extractor = FnExtractor::new("test::get_user"); let args = Args((UserId(42),)); - let key_parts = extractor.get(args).await; + let key_parts = extractor.get(args, &mut hitbox::EvalContext::new()).await; let (_, key) = key_parts.into_cache_key(); let key_str = key.to_string(); @@ -139,7 +139,7 @@ async fn test_extractor_with_multiple_derived_types() { let extractor = FnExtractor::new("test::get_user_for_tenant"); let args = Args((UserId(1), TenantId("acme".into()))); - let key_parts = extractor.get(args).await; + let key_parts = extractor.get(args, &mut hitbox::EvalContext::new()).await; let (_, key) = key_parts.into_cache_key(); let key_str = key.to_string(); @@ -163,8 +163,14 @@ async fn test_extractor_skip_not_affect_key() { request_id: "req-222".into(), },)); - let (_, key1) = extractor.get(args1).await.into_cache_key(); - let (_, key2) = extractor.get(args2).await.into_cache_key(); + let (_, key1) = extractor + .get(args1, &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); + let (_, key2) = extractor + .get(args2, &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); // Keys should be equal despite different request_id assert_eq!(key1.to_string(), key2.to_string()); @@ -174,8 +180,14 @@ async fn test_extractor_skip_not_affect_key() { async fn test_extractor_different_values_different_keys() { let extractor = FnExtractor::new("test::get_user"); - let (_, key1) = extractor.get(Args((UserId(1),))).await.into_cache_key(); - let (_, key2) = extractor.get(Args((UserId(2),))).await.into_cache_key(); + let (_, key1) = extractor + .get(Args((UserId(1),)), &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); + let (_, key2) = extractor + .get(Args((UserId(2),)), &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); assert_ne!(key1.to_string(), key2.to_string()); } @@ -300,7 +312,7 @@ async fn test_extractor_with_scalars() { let extractor = FnExtractor::new("test::compute"); let args = Args((42u64, "key".to_string())); - let key_parts = extractor.get(args).await; + let key_parts = extractor.get(args, &mut hitbox::EvalContext::new()).await; let (_, key) = key_parts.into_cache_key(); let key_str = key.to_string(); @@ -313,9 +325,18 @@ async fn test_extractor_with_scalars() { async fn test_extractor_scalar_different_values() { let extractor = FnExtractor::new("test::add"); - let (_, key1) = extractor.get(Args((1i64, 2i64))).await.into_cache_key(); - let (_, key2) = extractor.get(Args((1i64, 3i64))).await.into_cache_key(); - let (_, key3) = extractor.get(Args((1i64, 2i64))).await.into_cache_key(); + let (_, key1) = extractor + .get(Args((1i64, 2i64)), &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); + let (_, key2) = extractor + .get(Args((1i64, 3i64)), &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); + let (_, key3) = extractor + .get(Args((1i64, 2i64)), &mut hitbox::EvalContext::new()) + .await + .into_cache_key(); // Different args = different keys assert_ne!(key1.to_string(), key2.to_string()); diff --git a/hitbox-http/CHANGELOG.md b/hitbox-http/CHANGELOG.md index 01ab58ec..9a1d000e 100644 --- a/hitbox-http/CHANGELOG.md +++ b/hitbox-http/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Breaking:** Unified Config-based extractor/predicate API with `Into` shorthands, `Transform::Truncate`, `Transforms::builder()` with typestate, and full 64-char SHA256 for `Hash` ([#202](https://github.com/hit-box/hitbox/pull/202)) - Adapted `CacheableRequest` impl to GAT-based `CachePolicyFuture` ([#206](https://github.com/hit-box/hitbox/pull/206)) - **Breaking:** `BufferedBody::Complete` and `collect()` API changed to carry optional trailers ([#261](https://github.com/hit-box/hitbox/pull/261)) +- All predicate and extractor implementations now accept `&mut EvalContext` +- Predicates are now checked before extractors in `CacheableHttpRequest::cache_policy` ## [0.2.1] - 2026-02-09 diff --git a/hitbox-http/benches/predicate_performance.rs b/hitbox-http/benches/predicate_performance.rs index cebecf78..b7b854e5 100644 --- a/hitbox-http/benches/predicate_performance.rs +++ b/hitbox-http/benches/predicate_performance.rs @@ -1,6 +1,7 @@ use bytes::Bytes; use criterion::{Criterion, criterion_group, criterion_main}; use hitbox::predicate::{Predicate, PredicateExt}; +use hitbox_core::EvalContext; use hitbox_http::predicates::{ NeutralRequestPredicate, request::{ @@ -56,7 +57,11 @@ fn bench_single_predicates(c: &mut Criterion) { let predicate = NeutralRequestPredicate::new().method(Method::GET); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -64,7 +69,11 @@ fn bench_single_predicates(c: &mut Criterion) { let predicate = NeutralRequestPredicate::new().path("/api/users/{user_id}".to_string()); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -75,7 +84,11 @@ fn bench_single_predicates(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -84,7 +97,11 @@ fn bench_single_predicates(c: &mut Criterion) { .header(HeaderOperation::Exist("authorization".parse().unwrap())); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -95,7 +112,11 @@ fn bench_single_predicates(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_complex_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -116,7 +137,11 @@ fn bench_small_chains(c: &mut Criterion) { .path("/api/users/{user_id}".to_string()); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -130,7 +155,11 @@ fn bench_small_chains(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -148,7 +177,11 @@ fn bench_small_chains(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_complex_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -158,7 +191,11 @@ fn bench_small_chains(c: &mut Criterion) { let predicate = left.or(right); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -166,7 +203,11 @@ fn bench_small_chains(c: &mut Criterion) { let predicate = NeutralRequestPredicate::new().method(Method::POST).not(); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -202,7 +243,11 @@ fn bench_medium_chains(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_complex_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -228,7 +273,11 @@ fn bench_medium_chains(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_complex_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -255,7 +304,11 @@ fn bench_medium_chains(c: &mut Criterion) { .query(QueryOperation::Eq("sort".to_string(), "date".to_string())); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_complex_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -281,7 +334,11 @@ fn bench_early_exit(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -296,7 +353,11 @@ fn bench_early_exit(c: &mut Criterion) { )); // Will fail b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -315,7 +376,11 @@ fn bench_early_exit(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -334,7 +399,11 @@ fn bench_predicate_type_comparison(c: &mut Criterion) { let predicate = NeutralRequestPredicate::new().method(Method::GET); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -342,7 +411,11 @@ fn bench_predicate_type_comparison(c: &mut Criterion) { let predicate = NeutralRequestPredicate::new().path("/api/users/{user_id}".to_string()); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -353,7 +426,11 @@ fn bench_predicate_type_comparison(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -362,7 +439,11 @@ fn bench_predicate_type_comparison(c: &mut Criterion) { .header(HeaderOperation::Exist("authorization".parse().unwrap())); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_simple_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); @@ -373,7 +454,11 @@ fn bench_predicate_type_comparison(c: &mut Criterion) { )); b.to_async(&rt).iter(|| async { let req = CacheableHttpRequest::from_request(create_complex_request()); - black_box(predicate.check(black_box(req)).await) + black_box( + predicate + .check(black_box(req), &mut EvalContext::new()) + .await, + ) }); }); diff --git a/hitbox-http/src/extractors/body.rs b/hitbox-http/src/extractors/body.rs index d7621379..7054d2d2 100644 --- a/hitbox-http/src/extractors/body.rs +++ b/hitbox-http/src/extractors/body.rs @@ -19,6 +19,7 @@ use std::fmt::Debug; use std::rc::Rc; use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::{Extractor, KeyPart, KeyParts}; use hyper::body::Body as HttpBody; use jaq_core::box_iter::box_once; @@ -682,7 +683,7 @@ where { type Subject = E::Subject; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, ctx: &mut EvalContext) -> KeyParts { let (parts, body) = subject.into_parts(); // Collect body @@ -692,7 +693,7 @@ where let request = CacheableHttpRequest::from_request(http::Request::from_parts( parts, error_body, )); - let mut key_parts = self.inner.get(request).await; + let mut key_parts = self.inner.get(request, ctx).await; key_parts.push(KeyPart::new("body", None::)); return key_parts; } @@ -733,7 +734,7 @@ where }; let request = CacheableHttpRequest::from_request(http::Request::from_parts(parts, body)); - let mut key_parts = self.inner.get(request).await; + let mut key_parts = self.inner.get(request, ctx).await; for part in extracted_parts { key_parts.push(part); } diff --git a/hitbox-http/src/extractors/header.rs b/hitbox-http/src/extractors/header.rs index 31d49c2c..f64f2d38 100644 --- a/hitbox-http/src/extractors/header.rs +++ b/hitbox-http/src/extractors/header.rs @@ -21,6 +21,7 @@ //! ``` use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::{Extractor, KeyPart, KeyParts}; use http::HeaderValue; use regex::Regex; @@ -221,7 +222,7 @@ where { type Subject = E::Subject; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, ctx: &mut EvalContext) -> KeyParts { let headers = &subject.parts().headers; let mut extracted_parts = Vec::new(); @@ -249,7 +250,7 @@ where } } - let mut parts = self.inner.get(subject).await; + let mut parts = self.inner.get(subject, ctx).await; parts.append(&mut extracted_parts); parts } diff --git a/hitbox-http/src/extractors/method.rs b/hitbox-http/src/extractors/method.rs index 42bb9b6d..a5fa4165 100644 --- a/hitbox-http/src/extractors/method.rs +++ b/hitbox-http/src/extractors/method.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::{Extractor, KeyPart, KeyParts}; use crate::CacheableHttpRequest; @@ -98,9 +99,9 @@ where { type Subject = E::Subject; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, ctx: &mut EvalContext) -> KeyParts { let method = subject.parts().method.to_string(); - let mut parts = self.inner.get(subject).await; + let mut parts = self.inner.get(subject, ctx).await; parts.push(KeyPart::new("method", Some(method))); parts } diff --git a/hitbox-http/src/extractors/mod.rs b/hitbox-http/src/extractors/mod.rs index 03f9d30d..44546bbc 100644 --- a/hitbox-http/src/extractors/mod.rs +++ b/hitbox-http/src/extractors/mod.rs @@ -55,6 +55,7 @@ use std::marker::PhantomData; use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::{Extractor, KeyParts}; use crate::CacheableHttpRequest; @@ -127,7 +128,7 @@ where { type Subject = CacheableHttpRequest; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, _ctx: &mut EvalContext) -> KeyParts { KeyParts::new(subject) } } diff --git a/hitbox-http/src/extractors/path.rs b/hitbox-http/src/extractors/path.rs index f23db7ca..230f9ad0 100644 --- a/hitbox-http/src/extractors/path.rs +++ b/hitbox-http/src/extractors/path.rs @@ -1,5 +1,6 @@ use actix_router::ResourceDef; use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::{Extractor, KeyPart, KeyParts}; use crate::CacheableHttpRequest; @@ -145,14 +146,14 @@ where { type Subject = E::Subject; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, ctx: &mut EvalContext) -> KeyParts { let mut path = actix_router::Path::new(subject.parts().uri.path()); self.resource.capture_match_info(&mut path); let mut matched_parts = path .iter() .map(|(key, value)| KeyPart::new(key, Some(value))) .collect::>(); - let mut parts = self.inner.get(subject).await; + let mut parts = self.inner.get(subject, ctx).await; parts.append(&mut matched_parts); parts } diff --git a/hitbox-http/src/extractors/query.rs b/hitbox-http/src/extractors/query.rs index 1728b18d..6a48b40e 100644 --- a/hitbox-http/src/extractors/query.rs +++ b/hitbox-http/src/extractors/query.rs @@ -22,6 +22,7 @@ //! ``` use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::{Extractor, KeyPart, KeyParts}; use regex::Regex; @@ -224,7 +225,7 @@ where { type Subject = E::Subject; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, ctx: &mut EvalContext) -> KeyParts { let query_map = subject .parts() .uri @@ -263,7 +264,7 @@ where } }; - let mut parts = self.inner.get(subject).await; + let mut parts = self.inner.get(subject, ctx).await; parts.append(&mut extracted_parts); parts } diff --git a/hitbox-http/src/extractors/version.rs b/hitbox-http/src/extractors/version.rs index a4d0e4e9..a81a6c25 100644 --- a/hitbox-http/src/extractors/version.rs +++ b/hitbox-http/src/extractors/version.rs @@ -4,6 +4,7 @@ //! in cache keys. use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::{Extractor, KeyPart, KeyParts}; use crate::CacheableHttpRequest; @@ -92,9 +93,9 @@ where { type Subject = E::Subject; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, ctx: &mut EvalContext) -> KeyParts { let version = format!("{:?}", subject.parts().version); - let mut parts = self.inner.get(subject).await; + let mut parts = self.inner.get(subject, ctx).await; parts.push(KeyPart::new("version", Some(version))); parts } diff --git a/hitbox-http/src/predicates/body/predicate.rs b/hitbox-http/src/predicates/body/predicate.rs index cf6fc2f5..c4563dc6 100644 --- a/hitbox-http/src/predicates/body/predicate.rs +++ b/hitbox-http/src/predicates/body/predicate.rs @@ -3,6 +3,7 @@ //! Provides [`Body`] predicate for matching request or response bodies. use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::predicate::{Predicate, PredicateResult}; use hyper::body::Body as HttpBody; @@ -88,8 +89,12 @@ where { type Subject = P::Subject; - async fn check(&self, subject: Self::Subject) -> PredicateResult { - let inner_result = self.inner.check(subject).await; + async fn check( + &self, + subject: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + let inner_result = self.inner.check(subject, ctx).await; let (was_cacheable, subject) = match inner_result { PredicateResult::Cacheable(s) => (true, s), diff --git a/hitbox-http/src/predicates/header/predicate.rs b/hitbox-http/src/predicates/header/predicate.rs index d1a98cc7..04c67432 100644 --- a/hitbox-http/src/predicates/header/predicate.rs +++ b/hitbox-http/src/predicates/header/predicate.rs @@ -1,6 +1,7 @@ //! Header predicate implementation. use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::predicate::{Predicate, PredicateResult}; use http::HeaderMap; @@ -101,8 +102,12 @@ where { type Subject = P::Subject; - async fn check(&self, subject: Self::Subject) -> PredicateResult { - match self.inner.check(subject).await { + async fn check( + &self, + subject: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.inner.check(subject, ctx).await { PredicateResult::Cacheable(subject) => { let is_cacheable = self.operation.check(subject.headers()); if is_cacheable { diff --git a/hitbox-http/src/predicates/request/method.rs b/hitbox-http/src/predicates/request/method.rs index dda8349d..d272ade2 100644 --- a/hitbox-http/src/predicates/request/method.rs +++ b/hitbox-http/src/predicates/request/method.rs @@ -1,5 +1,6 @@ use crate::CacheableHttpRequest; use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::predicate::{Predicate, PredicateResult}; /// Matching operations for HTTP methods. @@ -106,8 +107,12 @@ where { type Subject = P::Subject; - async fn check(&self, request: Self::Subject) -> PredicateResult { - match self.inner.check(request).await { + async fn check( + &self, + request: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.inner.check(request, ctx).await { PredicateResult::Cacheable(request) => { let is_cacheable = match &self.operation { Operation::Eq(method) => *method == request.parts().method, diff --git a/hitbox-http/src/predicates/request/path.rs b/hitbox-http/src/predicates/request/path.rs index 5d772769..6bb50d6b 100644 --- a/hitbox-http/src/predicates/request/path.rs +++ b/hitbox-http/src/predicates/request/path.rs @@ -6,6 +6,7 @@ use crate::CacheableHttpRequest; use actix_router::ResourceDef; use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::predicate::{Predicate, PredicateResult}; /// Matching operations for request paths. @@ -116,8 +117,12 @@ where { type Subject = P::Subject; - async fn check(&self, request: Self::Subject) -> PredicateResult { - match self.inner.check(request).await { + async fn check( + &self, + request: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.inner.check(request, ctx).await { PredicateResult::Cacheable(request) => { let is_match = match &self.operation { Operation::Pattern(resource) => resource.is_match(request.parts().uri.path()), diff --git a/hitbox-http/src/predicates/request/query.rs b/hitbox-http/src/predicates/request/query.rs index 115e8b53..a514cfb1 100644 --- a/hitbox-http/src/predicates/request/query.rs +++ b/hitbox-http/src/predicates/request/query.rs @@ -4,6 +4,7 @@ use crate::CacheableHttpRequest; use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::predicate::{Predicate, PredicateResult}; /// Operations for matching query parameters. @@ -123,8 +124,12 @@ where { type Subject = P::Subject; - async fn check(&self, request: Self::Subject) -> PredicateResult { - match self.inner.check(request).await { + async fn check( + &self, + request: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.inner.check(request, ctx).await { PredicateResult::Cacheable(request) => { let is_cacheable = match request.parts().uri.query().and_then(crate::query::parse) { Some(query_map) => match &self.operation { diff --git a/hitbox-http/src/predicates/response/status.rs b/hitbox-http/src/predicates/response/status.rs index c75b3387..97d15037 100644 --- a/hitbox-http/src/predicates/response/status.rs +++ b/hitbox-http/src/predicates/response/status.rs @@ -1,5 +1,6 @@ use crate::CacheableHttpResponse; use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::predicate::{Predicate, PredicateResult}; /// HTTP status code classes for broad matching. @@ -191,8 +192,12 @@ where { type Subject = P::Subject; - async fn check(&self, response: Self::Subject) -> PredicateResult { - match self.inner.check(response).await { + async fn check( + &self, + response: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.inner.check(response, ctx).await { PredicateResult::Cacheable(response) => { if self.operation.matches(response.parts.status) { PredicateResult::Cacheable(response) diff --git a/hitbox-http/src/predicates/version/predicate.rs b/hitbox-http/src/predicates/version/predicate.rs index b0b859b8..5889dd92 100644 --- a/hitbox-http/src/predicates/version/predicate.rs +++ b/hitbox-http/src/predicates/version/predicate.rs @@ -1,6 +1,7 @@ //! HTTP version predicate implementation. use async_trait::async_trait; +use hitbox::EvalContext; use hitbox::predicate::{Predicate, PredicateResult}; use http::Version; @@ -85,8 +86,12 @@ where { type Subject = P::Subject; - async fn check(&self, subject: Self::Subject) -> PredicateResult { - match self.inner.check(subject).await { + async fn check( + &self, + subject: Self::Subject, + ctx: &mut EvalContext, + ) -> PredicateResult { + match self.inner.check(subject, ctx).await { PredicateResult::Cacheable(subject) => { if self.operation.check(subject.http_version()) { PredicateResult::Cacheable(subject) diff --git a/hitbox-http/src/request/mod.rs b/hitbox-http/src/request/mod.rs index c0512c95..93843d21 100644 --- a/hitbox-http/src/request/mod.rs +++ b/hitbox-http/src/request/mod.rs @@ -1,6 +1,7 @@ pub use crate::extractors::extractor; pub use crate::predicates::request::predicate; +use hitbox::EvalContext; use hitbox::{ CacheablePolicyData, RequestCachePolicy, predicate::{Predicate, PredicateResult}, @@ -63,7 +64,7 @@ use crate::predicates::version::HasVersion; /// .method(MethodConfig::new()) /// .path("/users/{user_id}"); /// # let _: &Path>>> = &extractor; -/// let key_parts = extractor.get(cacheable).await; +/// let key_parts = extractor.get(cacheable, &mut hitbox::EvalContext::new()).await; /// } /// ``` #[derive(Debug)] @@ -167,10 +168,10 @@ where E: Extractor + Send + Sync + 'a, { Box::pin(async move { - let (request, key) = extractors.get(self).await.into_cache_key(); - - match predicates.check(request).await { + let mut ctx = EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(request) => { + let (request, key) = extractors.get(request, &mut ctx).await.into_cache_key(); CachePolicy::Cacheable(CacheablePolicyData { key, request }) } PredicateResult::NonCacheable(request) => CachePolicy::NonCacheable(request), diff --git a/hitbox-http/src/response/mod.rs b/hitbox-http/src/response/mod.rs index 735b4943..fcc1a807 100644 --- a/hitbox-http/src/response/mod.rs +++ b/hitbox-http/src/response/mod.rs @@ -7,6 +7,7 @@ use bytes::Bytes; use chrono::Utc; use futures::FutureExt; use futures::future::BoxFuture; +use hitbox::EvalContext; use hitbox::{ CachePolicy, CacheValue, CacheableResponse, EntityPolicyConfig, predicate::PredicateResult, }; @@ -421,7 +422,8 @@ where where P: hitbox::Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(cacheable) => match cacheable.into_cached().await { CachePolicy::Cacheable(res) => CachePolicy::Cacheable(CacheValue::new( res, diff --git a/hitbox-http/tests/body.rs b/hitbox-http/tests/body.rs index f5384d87..7bc9391d 100644 --- a/hitbox-http/tests/body.rs +++ b/hitbox-http/tests/body.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::predicate::{Predicate, PredicateResult}; +use hitbox_core::EvalContext; use hitbox_http::predicates::NeutralRequestPredicate; use hitbox_http::predicates::request::BodyPredicate; use hitbox_http::predicates::request::body::{JqExpression, JqOperation, Operation}; @@ -28,7 +29,7 @@ mod eq_tests { operation: JqOperation::Eq("test-value".into()), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -47,7 +48,7 @@ mod eq_tests { operation: JqOperation::Eq("wrong-value".into()), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -65,7 +66,7 @@ mod eq_tests { operation: JqOperation::Eq("test-value".into()), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -89,7 +90,7 @@ mod exist_tests { operation: JqOperation::Exist, }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -107,7 +108,7 @@ mod exist_tests { operation: JqOperation::Exist, }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -132,7 +133,7 @@ mod in_tests { operation: JqOperation::In(values.into_iter().map(|v| v.into()).collect()), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -151,7 +152,7 @@ mod in_tests { operation: JqOperation::In(values.into_iter().map(|v| v.into()).collect()), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -171,7 +172,7 @@ async fn test_request_body_predicates_positive_basic() { operation: JqOperation::Eq("value_one".into()), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -194,7 +195,7 @@ async fn test_request_body_predicates_positive_array() { operation: JqOperation::Eq("my-key-01".into()), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -218,6 +219,6 @@ async fn test_request_body_predicates_positive_multiple_value() { operation: JqOperation::Eq(json!(["my-key-00", "my-key-01", "my-key-02"])), }); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } diff --git a/hitbox-http/tests/extractors/body.rs b/hitbox-http/tests/extractors/body.rs index a9d9a0b4..feee53a2 100644 --- a/hitbox-http/tests/extractors/body.rs +++ b/hitbox-http/tests/extractors/body.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::Extractor; +use hitbox_core::EvalContext; use hitbox_http::extractors::body::{ BodyConfig, BodyExtraction, BodyExtractor, IntoBodyExtraction, JqExtraction, RegexExtraction, Transforms, @@ -104,7 +105,7 @@ async fn test_body_extractor_hash_mode() { let extractor = NeutralExtractor::new() .method(MethodConfig::new()) .body(BodyConfig::new().hash()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let body_part = key_parts.iter().find(|p| p.key() == "body").unwrap(); @@ -125,7 +126,7 @@ async fn test_body_extractor_jq_mode() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().body(BodyConfig::new().jq(".user").unwrap()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let body_part = key_parts.iter().find(|p| p.key() == "body").unwrap(); @@ -141,7 +142,7 @@ async fn test_body_extractor_jq_mode_object_result() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().body(BodyConfig::new().jq(".user").unwrap()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); assert!(key_parts.iter().any(|p| p.key() == "name")); @@ -157,7 +158,7 @@ async fn test_body_extractor_jq_mode_edge_cases() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().body(BodyConfig::new().jq(".field").unwrap()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let body_part = key_parts.iter().find(|p| p.key() == "body").unwrap(); @@ -170,7 +171,7 @@ async fn test_body_extractor_jq_mode_edge_cases() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().body(BodyConfig::new().jq(".field").unwrap()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let body_part = key_parts.iter().find(|p| p.key() == "body").unwrap(); @@ -183,7 +184,7 @@ async fn test_body_extractor_jq_mode_edge_cases() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().body(BodyConfig::new().jq(".active").unwrap()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let body_part = key_parts.iter().find(|p| p.key() == "body").unwrap(); @@ -206,7 +207,7 @@ async fn test_body_extractor_jq_hash_types() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().body(BodyConfig::new().jq(".val | hash").unwrap()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let body_part = key_parts @@ -233,7 +234,7 @@ async fn test_body_extractor_regex_mode_single_match() { .regex(r"order_id: (\w+)") .unwrap(), ); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let order_part = key_parts.iter().find(|p| p.key() == "order").unwrap(); @@ -255,7 +256,7 @@ async fn test_body_extractor_regex_mode_global() { .unwrap() .global(), ); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let id_parts: Vec<_> = key_parts.iter().filter(|p| p.key() == "id").collect(); @@ -277,7 +278,7 @@ async fn test_body_extractor_regex_mode_named_groups() { transforms: Transforms::None, }); let extractor = NeutralExtractor::new().body(extraction); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); assert!(key_parts.iter().any(|p| p.key() == "user")); diff --git a/hitbox-http/tests/extractors/header.rs b/hitbox-http/tests/extractors/header.rs index 0dc666d7..536d1e1b 100644 --- a/hitbox-http/tests/extractors/header.rs +++ b/hitbox-http/tests/extractors/header.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::Extractor; +use hitbox_core::EvalContext; use hitbox_http::extractors::NeutralExtractor; use hitbox_http::extractors::header::{ HeaderConfig, HeaderExtractor, NameSelector, ValueExtractor, @@ -17,7 +18,7 @@ async fn test_request_header_extractor_some() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().header("x-test"); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let header_part = key_parts.iter().find(|p| p.key() == "x-test").unwrap(); @@ -32,7 +33,7 @@ async fn test_request_header_extractor_from_string() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().header(String::from("x-api-key")); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let header_part = key_parts.iter().find(|p| p.key() == "x-api-key").unwrap(); @@ -50,7 +51,7 @@ async fn test_request_header_extractor_starts_with() { let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().header(HeaderConfig::name(NameSelector::starts("x-custom"))); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); // Should match x-custom-one and x-custom-two, but not x-other @@ -70,7 +71,7 @@ async fn test_request_header_extractor_with_regex_value() { NeutralExtractor::new().header(HeaderConfig::name(NameSelector::exact("accept")).value( ValueExtractor::Regex(regex::Regex::new(r"version=(\d+)").unwrap()), )); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let accept_part = key_parts.iter().find(|p| p.key() == "accept").unwrap(); @@ -86,7 +87,7 @@ async fn test_request_header_extractor_with_transform() { let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new() .header(HeaderConfig::name(NameSelector::exact("x-token")).transform(Transform::Lowercase)); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let token_part = key_parts.iter().find(|p| p.key() == "x-token").unwrap(); diff --git a/hitbox-http/tests/extractors/method.rs b/hitbox-http/tests/extractors/method.rs index 9299a744..712467f1 100644 --- a/hitbox-http/tests/extractors/method.rs +++ b/hitbox-http/tests/extractors/method.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::Extractor; +use hitbox_core::EvalContext; use hitbox_http::extractors::{MethodConfig, NeutralExtractor, method::MethodExtractor}; use hitbox_http::{BufferedBody, CacheableHttpRequest}; use http::{Method, Request}; @@ -14,6 +15,6 @@ async fn test_request_method_extractor_some() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().method(MethodConfig::new()); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; dbg!(parts); } diff --git a/hitbox-http/tests/extractors/multiple.rs b/hitbox-http/tests/extractors/multiple.rs index 318d9518..de70b94e 100644 --- a/hitbox-http/tests/extractors/multiple.rs +++ b/hitbox-http/tests/extractors/multiple.rs @@ -21,6 +21,8 @@ async fn test_request_multiple_extractor_some() { .path("/users/{user_id}/books/{book_id}/") .method(MethodConfig::new()) .header("x-test".to_owned()); - let parts = extractor.get(request).await; + let parts = extractor + .get(request, &mut hitbox_core::EvalContext::new()) + .await; dbg!(parts); } diff --git a/hitbox-http/tests/extractors/path.rs b/hitbox-http/tests/extractors/path.rs index b9eb7f65..8dd0622b 100644 --- a/hitbox-http/tests/extractors/path.rs +++ b/hitbox-http/tests/extractors/path.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::Extractor; +use hitbox_core::EvalContext; use hitbox_http::extractors::{NeutralExtractor, path::PathExtractor}; use hitbox_http::{BufferedBody, CacheableHttpRequest}; use http::Request; @@ -13,7 +14,7 @@ async fn test_request_path_extractor_some() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().path("/users/{user_id}/books/{book_id}/"); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let user_part = key_parts.iter().find(|p| p.key() == "user_id").unwrap(); @@ -31,7 +32,7 @@ async fn test_request_path_extractor_from_owned_string_ref() { let request = CacheableHttpRequest::from_request(request); let pattern = String::from("/api/{version}/items"); let extractor = NeutralExtractor::new().path(&pattern); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let version_part = key_parts.iter().find(|p| p.key() == "version").unwrap(); diff --git a/hitbox-http/tests/extractors/query.rs b/hitbox-http/tests/extractors/query.rs index 67252e8b..6ca14778 100644 --- a/hitbox-http/tests/extractors/query.rs +++ b/hitbox-http/tests/extractors/query.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::Extractor; +use hitbox_core::EvalContext; use hitbox_http::extractors::NeutralExtractor; use hitbox_http::extractors::query::{NameSelector, QueryConfig, QueryExtractor, ValueExtractor}; use hitbox_http::extractors::transform::Transform; @@ -19,7 +20,7 @@ async fn test_request_query_extractor_some() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().query("key"); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let query_part = key_parts.iter().find(|p| p.key() == "key").unwrap(); @@ -38,7 +39,7 @@ async fn test_request_query_extractor_none() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().query("non-existent-key"); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); assert!(key_parts.is_empty()); @@ -56,7 +57,7 @@ async fn test_request_query_extractor_multiple() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().query("cars"); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let car_parts: Vec<_> = key_parts.iter().filter(|p| p.key() == "cars").collect(); @@ -75,7 +76,7 @@ async fn test_request_query_extractor_from_string() { .unwrap(); let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().query(String::from("page")); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let page_part = key_parts.iter().find(|p| p.key() == "page").unwrap(); @@ -95,7 +96,7 @@ async fn test_request_query_extractor_starts_with() { let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new().query(QueryConfig::name(NameSelector::starts("filter_"))); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); // Should match filter_name and filter_role, not page @@ -119,7 +120,7 @@ async fn test_request_query_extractor_with_regex_value() { QueryConfig::name(NameSelector::exact("version")) .value(ValueExtractor::Regex(regex::Regex::new(r"v(\d+)").unwrap())), ); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let version_part = key_parts.iter().find(|p| p.key() == "version").unwrap(); @@ -139,7 +140,7 @@ async fn test_request_query_extractor_with_transform() { let request = CacheableHttpRequest::from_request(request); let extractor = NeutralExtractor::new() .query(QueryConfig::name(NameSelector::exact("search")).transform(Transform::Lowercase)); - let parts = extractor.get(request).await; + let parts = extractor.get(request, &mut EvalContext::new()).await; let (_subject, cache_key) = parts.into_cache_key(); let key_parts: Vec<_> = cache_key.parts().collect(); let search_part = key_parts.iter().find(|p| p.key() == "search").unwrap(); diff --git a/hitbox-http/tests/predicates/conditions.rs b/hitbox-http/tests/predicates/conditions.rs index b4005519..af090fb3 100644 --- a/hitbox-http/tests/predicates/conditions.rs +++ b/hitbox-http/tests/predicates/conditions.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::predicate::{Predicate, PredicateExt, PredicateResult}; +use hitbox_core::EvalContext; use hitbox_http::predicates::NeutralRequestPredicate; use hitbox_http::predicates::request::header; use hitbox_http::predicates::request::query; @@ -22,7 +23,10 @@ async fn test_conditions_or_cacheable() { NeutralRequestPredicate::new().method(method::Operation::eq(http::Method::GET)); let wrong_predicate = NeutralRequestPredicate::new().method(method::Operation::eq(http::Method::POST)); - let prediction = correct_predicate.or(wrong_predicate).check(request).await; + let prediction = correct_predicate + .or(wrong_predicate) + .check(request, &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -39,7 +43,10 @@ async fn test_conditions_or_right_branch() { NeutralRequestPredicate::new().method(method::Operation::eq(http::Method::POST)); let correct_predicate = NeutralRequestPredicate::new().method(method::Operation::eq(http::Method::GET)); - let prediction = wrong_predicate.or(correct_predicate).check(request).await; + let prediction = wrong_predicate + .or(correct_predicate) + .check(request, &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -57,7 +64,7 @@ async fn test_conditions_or_noncacheable() { NeutralRequestPredicate::new().method(method::Operation::eq(http::Method::POST)); let prediction = wrong_predicate_one .or(wrong_predicate_two) - .check(request) + .check(request, &mut EvalContext::new()) .await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -85,7 +92,7 @@ async fn test_conditions_not() { let prediction = correct_query_predicate .and(wrong_path_predicate.not()) .and(wrong_header_predicate.not()) - .check(request) + .check(request, &mut EvalContext::new()) .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } diff --git a/hitbox-http/tests/predicates/request/header.rs b/hitbox-http/tests/predicates/request/header.rs index b51e321f..d0be81be 100644 --- a/hitbox-http/tests/predicates/request/header.rs +++ b/hitbox-http/tests/predicates/request/header.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::predicate::{Predicate, PredicateResult}; +use hitbox_core::EvalContext; use hitbox_http::predicates::NeutralRequestPredicate; use hitbox_http::predicates::request::HeaderPredicate; use hitbox_http::predicates::request::header::Operation; @@ -22,7 +23,7 @@ mod eq_tests { let name: HeaderName = "x-test".to_string().parse().unwrap(); let value: HeaderValue = "test-value".to_string().parse().unwrap(); let predicate = NeutralRequestPredicate::new().header(Operation::Eq(name, value)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -36,7 +37,7 @@ mod eq_tests { let name: HeaderName = "x-test".to_string().parse().unwrap(); let value: HeaderValue = "wrong-value".to_string().parse().unwrap(); let predicate = NeutralRequestPredicate::new().header(Operation::Eq(name, value)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -50,7 +51,7 @@ mod eq_tests { let name: HeaderName = "wrong-name".to_string().parse().unwrap(); let value: HeaderValue = "test-value".to_string().parse().unwrap(); let predicate = NeutralRequestPredicate::new().header(Operation::Eq(name, value)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -68,7 +69,7 @@ mod exist_tests { let request = CacheableHttpRequest::from_request(request); let name: HeaderName = "x-test".to_string().parse().unwrap(); let predicate = NeutralRequestPredicate::new().header(Operation::Exist(name)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -80,7 +81,7 @@ mod exist_tests { let request = CacheableHttpRequest::from_request(request); let name: HeaderName = "x-test".to_string().parse().unwrap(); let predicate = NeutralRequestPredicate::new().header(Operation::Exist(name)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -102,7 +103,7 @@ mod in_tests { "test-value".to_string().parse().unwrap(), ]; let predicate = NeutralRequestPredicate::new().header(Operation::In(name, values)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -119,7 +120,7 @@ mod in_tests { "test-value".to_string().parse().unwrap(), ]; let predicate = NeutralRequestPredicate::new().header(Operation::In(name, values)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -137,7 +138,7 @@ mod contains_tests { let request = CacheableHttpRequest::from_request(request); let predicate = NeutralRequestPredicate::new() .header(Operation::contains("content-type".parse().unwrap(), "json")); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -150,7 +151,7 @@ mod contains_tests { let request = CacheableHttpRequest::from_request(request); let predicate = NeutralRequestPredicate::new() .header(Operation::contains("content-type".parse().unwrap(), "json")); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -170,7 +171,7 @@ mod regex_tests { "accept".parse().unwrap(), Regex::new(r"version=\d+").unwrap(), )); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -185,7 +186,7 @@ mod regex_tests { "accept".parse().unwrap(), Regex::new(r"version=\d+").unwrap(), )); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -208,14 +209,22 @@ mod constructor_and_from_tests { let predicate = NeutralRequestPredicate::new() .header(Operation::eq("x-test".parse().unwrap(), "test-value").unwrap()); let prediction = predicate - .check(request_with_header("x-test", "test-value")) + .check( + request_with_header("x-test", "test-value"), + &mut EvalContext::new(), + ) .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // exist let predicate = NeutralRequestPredicate::new().header(Operation::exist("x-test".parse().unwrap())); - let prediction = predicate.check(request_with_header("x-test", "any")).await; + let prediction = predicate + .check( + request_with_header("x-test", "any"), + &mut EvalContext::new(), + ) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // any @@ -224,7 +233,10 @@ mod constructor_and_from_tests { vec!["val-a".parse().unwrap(), "val-b".parse().unwrap()], )); let prediction = predicate - .check(request_with_header("x-test", "val-b")) + .check( + request_with_header("x-test", "val-b"), + &mut EvalContext::new(), + ) .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); @@ -232,7 +244,10 @@ mod constructor_and_from_tests { let predicate = NeutralRequestPredicate::new() .header(Operation::regex("x-version".parse().unwrap(), r"v\d+\.\d+").unwrap()); let prediction = predicate - .check(request_with_header("x-version", "v2.1")) + .check( + request_with_header("x-version", "v2.1"), + &mut EvalContext::new(), + ) .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -243,7 +258,10 @@ mod constructor_and_from_tests { let name: HeaderName = "authorization".parse().unwrap(); let predicate = NeutralRequestPredicate::new().header(Operation::from(name)); let prediction = predicate - .check(request_with_header("authorization", "Bearer xyz")) + .check( + request_with_header("authorization", "Bearer xyz"), + &mut EvalContext::new(), + ) .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); @@ -252,7 +270,10 @@ mod constructor_and_from_tests { let value: HeaderValue = "exact-value".parse().unwrap(); let predicate = NeutralRequestPredicate::new().header(Operation::from((name, value))); let prediction = predicate - .check(request_with_header("x-test", "exact-value")) + .check( + request_with_header("x-test", "exact-value"), + &mut EvalContext::new(), + ) .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } diff --git a/hitbox-http/tests/predicates/request/path.rs b/hitbox-http/tests/predicates/request/path.rs index c8e22f8d..ee0bd371 100644 --- a/hitbox-http/tests/predicates/request/path.rs +++ b/hitbox-http/tests/predicates/request/path.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::predicate::{Predicate, PredicateResult}; +use hitbox_core::EvalContext; use hitbox_http::predicates::NeutralRequestPredicate; use hitbox_http::predicates::request::{PathPredicate, path}; use hitbox_http::{BufferedBody, CacheableHttpRequest}; @@ -17,7 +18,7 @@ async fn test_request_path_predicates_full_match() { .unwrap(), ); let predicate = NeutralRequestPredicate::new().path(path::Operation::pattern(expression)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -32,7 +33,7 @@ async fn test_request_path_predicates_use_expression() { .unwrap(), ); let predicate = NeutralRequestPredicate::new().path(path::Operation::pattern(expression)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -47,7 +48,7 @@ async fn test_request_path_predicates_non_match() { .unwrap(), ); let predicate = NeutralRequestPredicate::new().path(path::Operation::pattern(expression)); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -62,7 +63,7 @@ async fn test_request_path_from_conversions() { ); let op: path::Operation = "/api/users/{id}".into(); let predicate = NeutralRequestPredicate::new().path(op); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // From @@ -74,6 +75,6 @@ async fn test_request_path_from_conversions() { ); let op: path::Operation = String::from("/api/users/{id}").into(); let predicate = NeutralRequestPredicate::new().path(op); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } diff --git a/hitbox-http/tests/predicates/request/query.rs b/hitbox-http/tests/predicates/request/query.rs index eb3490dc..62bd11c9 100644 --- a/hitbox-http/tests/predicates/request/query.rs +++ b/hitbox-http/tests/predicates/request/query.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::predicate::{Predicate, PredicateResult}; +use hitbox_core::EvalContext; use hitbox_http::predicates::NeutralRequestPredicate; use hitbox_http::predicates::request::QueryPredicate; use hitbox_http::predicates::request::query; @@ -18,7 +19,7 @@ async fn test_request_query_predicates_positive() { ); let predicate = NeutralRequestPredicate::new() .query(query::Operation::Eq("name".to_owned(), "value".to_owned())); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -35,7 +36,7 @@ async fn test_request_query_predicates_multiple() { "name".to_owned(), vec!["value".to_owned(), "second-value".to_owned()], )); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -52,7 +53,7 @@ async fn test_request_query_predicates_negative() { "name".to_owned(), "wrong-value".to_owned(), )); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -67,7 +68,7 @@ async fn test_request_query_from_conversions() { ); let op: query::Operation = "name".into(); let predicate = NeutralRequestPredicate::new().query(op); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // From<(&str, &str)> creates an Eq operation @@ -79,7 +80,7 @@ async fn test_request_query_from_conversions() { ); let op: query::Operation = ("format", "json").into(); let predicate = NeutralRequestPredicate::new().query(op); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -95,7 +96,7 @@ async fn test_request_query_constructors() { let predicate = NeutralRequestPredicate::new() .query(query::Operation::eq("page", "3")) .query(query::Operation::exist("limit")); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // any constructor @@ -109,6 +110,6 @@ async fn test_request_query_constructors() { "sort", vec!["asc".to_owned(), "desc".to_owned()], )); - let prediction = predicate.check(request).await; + let prediction = predicate.check(request, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } diff --git a/hitbox-http/tests/predicates/response/body.rs b/hitbox-http/tests/predicates/response/body.rs index 70c1d527..2ca12743 100644 --- a/hitbox-http/tests/predicates/response/body.rs +++ b/hitbox-http/tests/predicates/response/body.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::predicate::{Predicate, PredicateResult}; +use hitbox_core::EvalContext; use hitbox_http::predicates::NeutralResponsePredicate; use hitbox_http::predicates::response::body::BodyPredicate; use hitbox_http::predicates::response::{JqExpression, JqOperation, Operation, PlainOperation}; @@ -27,7 +28,7 @@ mod jq_eq_tests { operation: JqOperation::Eq(json!("test-value")), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -46,7 +47,7 @@ mod jq_eq_tests { operation: JqOperation::Eq(json!("wrong-value")), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -65,7 +66,7 @@ mod jq_eq_tests { operation: JqOperation::Eq(json!("test-value")), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -90,7 +91,7 @@ mod jq_exist_tests { operation: JqOperation::Exist, }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -109,7 +110,7 @@ mod jq_exist_tests { operation: JqOperation::Exist, }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -135,7 +136,7 @@ mod jq_in_tests { operation: JqOperation::In(values), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -155,7 +156,7 @@ mod jq_in_tests { operation: JqOperation::In(values), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } } @@ -176,7 +177,7 @@ async fn test_response_body_jq_nested_field() { operation: JqOperation::Eq(json!("value_one")), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -200,7 +201,7 @@ async fn test_response_body_jq_array_index() { operation: JqOperation::Eq(json!("my-key-01")), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -225,7 +226,7 @@ async fn test_response_body_jq_array_map() { operation: JqOperation::Eq(json!(["my-key-00", "my-key-01", "my-key-02"])), }); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -244,7 +245,7 @@ mod constructor_tests { .unwrap(), ); let predicate = NeutralResponsePredicate::new().body(Operation::limit(1024)); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // non-cacheable (exceeds limit) @@ -255,7 +256,7 @@ mod constructor_tests { .unwrap(), ); let predicate = NeutralResponsePredicate::new().body(Operation::limit(10)); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -270,7 +271,7 @@ mod constructor_tests { ); let predicate = NeutralResponsePredicate::new() .body(Operation::plain(PlainOperation::Contains("success".into()))); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // jq constructor @@ -282,7 +283,7 @@ mod constructor_tests { ); let predicate = NeutralResponsePredicate::new() .body(Operation::jq(".status", JqOperation::Eq(json!("ok"))).unwrap()); - let prediction = predicate.check(response).await; + let prediction = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } } diff --git a/hitbox-http/tests/predicates/response/plain_operation.rs b/hitbox-http/tests/predicates/response/plain_operation.rs index 9bd70fa5..302cf076 100644 --- a/hitbox-http/tests/predicates/response/plain_operation.rs +++ b/hitbox-http/tests/predicates/response/plain_operation.rs @@ -20,6 +20,7 @@ fn create_stream_body( mod eq_tests { use super::*; use hitbox::predicate::{Predicate, PredicateResult}; + use hitbox_core::EvalContext; use hitbox_http::CacheableHttpResponse; use hitbox_http::predicates::NeutralResponsePredicate; use hitbox_http::predicates::response::BodyPredicate; @@ -38,7 +39,7 @@ mod eq_tests { Bytes::copy_from_slice(b"hello world"), ))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -54,7 +55,7 @@ mod eq_tests { Bytes::copy_from_slice(b"goodbye world"), ))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::NonCacheable(_))); } @@ -70,7 +71,7 @@ mod eq_tests { Bytes::copy_from_slice(b"hello world"), ))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -85,7 +86,7 @@ mod eq_tests { let predicate = NeutralResponsePredicate::new() .body(Operation::Plain(PlainOperation::Eq(Bytes::new()))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } } @@ -94,6 +95,7 @@ mod eq_tests { mod contains_tests { use super::*; use hitbox::predicate::{Predicate, PredicateResult}; + use hitbox_core::EvalContext; use hitbox_http::CacheableHttpResponse; use hitbox_http::predicates::NeutralResponsePredicate; use hitbox_http::predicates::response::BodyPredicate; @@ -112,7 +114,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -128,7 +130,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"goodbye")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::NonCacheable(_))); } @@ -144,7 +146,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"hello")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -160,7 +162,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -175,7 +177,7 @@ mod contains_tests { let predicate = NeutralResponsePredicate::new() .body(Operation::Plain(PlainOperation::Contains(Bytes::new()))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; // Empty pattern should always match assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -194,7 +196,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"lo wo")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -212,7 +214,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -229,7 +231,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -246,7 +248,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"hello")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -263,7 +265,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"llo")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -282,7 +284,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -301,7 +303,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -320,7 +322,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -344,7 +346,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; // Pattern found in prefix, but should be NonCacheable due to error assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -369,7 +371,7 @@ mod contains_tests { PlainOperation::Contains(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::NonCacheable(_))); } } @@ -378,6 +380,7 @@ mod contains_tests { mod starts_with_tests { use super::*; use hitbox::predicate::{Predicate, PredicateResult}; + use hitbox_core::EvalContext; use hitbox_http::CacheableHttpResponse; use hitbox_http::predicates::NeutralResponsePredicate; use hitbox_http::predicates::response::BodyPredicate; @@ -396,7 +399,7 @@ mod starts_with_tests { PlainOperation::Starts(Bytes::copy_from_slice(b"hello")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -412,7 +415,7 @@ mod starts_with_tests { PlainOperation::Starts(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::NonCacheable(_))); } @@ -428,7 +431,7 @@ mod starts_with_tests { PlainOperation::Starts(Bytes::copy_from_slice(b"hello")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -443,7 +446,7 @@ mod starts_with_tests { let predicate = NeutralResponsePredicate::new() .body(Operation::Plain(PlainOperation::Starts(Bytes::new()))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -459,7 +462,7 @@ mod starts_with_tests { PlainOperation::Starts(Bytes::copy_from_slice(b"hello")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::NonCacheable(_))); } } @@ -468,6 +471,7 @@ mod starts_with_tests { mod ends_with_tests { use super::*; use hitbox::predicate::{Predicate, PredicateResult}; + use hitbox_core::EvalContext; use hitbox_http::CacheableHttpResponse; use hitbox_http::predicates::NeutralResponsePredicate; use hitbox_http::predicates::response::BodyPredicate; @@ -486,7 +490,7 @@ mod ends_with_tests { PlainOperation::Ends(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -502,7 +506,7 @@ mod ends_with_tests { PlainOperation::Ends(Bytes::copy_from_slice(b"hello")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::NonCacheable(_))); } @@ -518,7 +522,7 @@ mod ends_with_tests { PlainOperation::Ends(Bytes::copy_from_slice(b"world")), )); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -533,7 +537,7 @@ mod ends_with_tests { let predicate = NeutralResponsePredicate::new() .body(Operation::Plain(PlainOperation::Ends(Bytes::new()))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } } @@ -542,6 +546,7 @@ mod ends_with_tests { mod regexp_tests { use super::*; use hitbox::predicate::{Predicate, PredicateResult}; + use hitbox_core::EvalContext; use hitbox_http::CacheableHttpResponse; use hitbox_http::predicates::NeutralResponsePredicate; use hitbox_http::predicates::response::BodyPredicate; @@ -560,7 +565,7 @@ mod regexp_tests { let predicate = NeutralResponsePredicate::new().body(Operation::Plain(PlainOperation::RegExp(regex))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } @@ -576,7 +581,7 @@ mod regexp_tests { let predicate = NeutralResponsePredicate::new().body(Operation::Plain(PlainOperation::RegExp(regex))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::NonCacheable(_))); } @@ -592,7 +597,7 @@ mod regexp_tests { let predicate = NeutralResponsePredicate::new().body(Operation::Plain(PlainOperation::RegExp(regex))); - let result = predicate.check(response).await; + let result = predicate.check(response, &mut EvalContext::new()).await; assert!(matches!(result, PredicateResult::Cacheable(_))); } } diff --git a/hitbox-http/tests/predicates/response/status.rs b/hitbox-http/tests/predicates/response/status.rs index 487d13e6..bf8a2eae 100644 --- a/hitbox-http/tests/predicates/response/status.rs +++ b/hitbox-http/tests/predicates/response/status.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use hitbox::predicate::{Predicate, PredicateResult}; +use hitbox_core::EvalContext; use hitbox_http::predicates::response::StatusClass; use hitbox_http::predicates::response::status::Operation as StatusOp; use hitbox_http::{ @@ -22,7 +23,9 @@ fn response(status: u16) -> CacheableHttpResponse> { async fn test_response_predicates_match() { let predicate = NeutralResponsePredicate::new().status(StatusOp::eq(StatusCode::from_u16(200).unwrap())); - let prediction = predicate.check(response(200)).await; + let prediction = predicate + .check(response(200), &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -30,7 +33,9 @@ async fn test_response_predicates_match() { async fn test_response_predicates_not_match() { let predicate = NeutralResponsePredicate::new().status(StatusOp::eq(StatusCode::from_u16(200).unwrap())); - let prediction = predicate.check(response(500)).await; + let prediction = predicate + .check(response(500), &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -43,7 +48,9 @@ async fn test_status_class_matching() { (503, StatusClass::ServerError), ] { let predicate = NeutralResponsePredicate::new().status(class); - let prediction = predicate.check(response(status)).await; + let prediction = predicate + .check(response(status), &mut EvalContext::new()) + .await; assert!( matches!(prediction, PredicateResult::Cacheable(_)), "expected Cacheable for status {status}" @@ -54,7 +61,9 @@ async fn test_status_class_matching() { #[tokio::test] async fn test_status_class_mismatch() { let predicate = NeutralResponsePredicate::new().status(StatusClass::ClientError); - let prediction = predicate.check(response(200)).await; + let prediction = predicate + .check(response(200), &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::NonCacheable(_))); } @@ -63,13 +72,17 @@ async fn test_status_from_conversions() { // From StatusCode let op: StatusOp = StatusCode::OK.into(); let predicate = NeutralResponsePredicate::new().status(op); - let prediction = predicate.check(response(200)).await; + let prediction = predicate + .check(response(200), &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); // From StatusClass let op: StatusOp = StatusClass::Success.into(); let predicate = NeutralResponsePredicate::new().status(op); - let prediction = predicate.check(response(201)).await; + let prediction = predicate + .check(response(201), &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -79,7 +92,9 @@ async fn test_status_range() { StatusCode::OK, StatusCode::from_u16(299).unwrap(), )); - let prediction = predicate.check(response(204)).await; + let prediction = predicate + .check(response(204), &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } @@ -87,6 +102,8 @@ async fn test_status_range() { async fn test_status_any() { let predicate = NeutralResponsePredicate::new() .status(StatusOp::any(vec![StatusCode::OK, StatusCode::CREATED])); - let prediction = predicate.check(response(201)).await; + let prediction = predicate + .check(response(201), &mut EvalContext::new()) + .await; assert!(matches!(prediction, PredicateResult::Cacheable(_))); } diff --git a/hitbox-tarantool/tests/integration_test.rs b/hitbox-tarantool/tests/integration_test.rs index 68a03ec5..b2b911d4 100644 --- a/hitbox-tarantool/tests/integration_test.rs +++ b/hitbox-tarantool/tests/integration_test.rs @@ -102,7 +102,8 @@ impl CacheableResponse for Test { where P: hitbox_core::Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = hitbox_core::EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(cacheable) => match cacheable.into_cached().await { CachePolicy::Cacheable(res) => { CachePolicy::Cacheable(CachedValue::new(res, Utc::now())) diff --git a/hitbox-test/benches/cache_future_reference.rs b/hitbox-test/benches/cache_future_reference.rs index 258703b0..c2a0851b 100644 --- a/hitbox-test/benches/cache_future_reference.rs +++ b/hitbox-test/benches/cache_future_reference.rs @@ -29,6 +29,7 @@ use hitbox_backend::format::BincodeFormat; use hitbox_backend::{CacheBackend, CompositionBackend, PassthroughCompressor}; use hitbox_configuration::{Backend as ConfigBackend, ConfigEndpoint}; use hitbox_core::DisabledOffload; +use hitbox_core::EvalContext; use hitbox_core::Upstream; use hitbox_http::extractors::MethodConfig; use hitbox_http::extractors::NeutralExtractor; @@ -272,7 +273,12 @@ fn bench_compare_cache_write(c: &mut Criterion) { // Generate cache key using extractors let extractors = create_extractors(); let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, cache_key) = rt.block_on(async { extractors.get(request).await.into_cache_key() }); + let (_, cache_key) = rt.block_on(async { + extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); // Generate cacheable response let response = CacheableHttpResponse::from_response(create_reference_response()); @@ -339,7 +345,12 @@ fn bench_compare_cache_read(c: &mut Criterion) { // Generate cache key using extractors let extractors = create_extractors(); let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, cache_key) = rt.block_on(async { extractors.get(request).await.into_cache_key() }); + let (_, cache_key) = rt.block_on(async { + extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); // Generate cacheable response let response = CacheableHttpResponse::from_response(create_reference_response()); @@ -439,7 +450,12 @@ fn bench_compare_composition_read(c: &mut Criterion) { // Generate cache key using extractors let extractors = create_extractors(); let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, cache_key) = rt.block_on(async { extractors.get(request).await.into_cache_key() }); + let (_, cache_key) = rt.block_on(async { + extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); // Generate cacheable response let response = CacheableHttpResponse::from_response(create_reference_response()); @@ -556,7 +572,12 @@ fn bench_compare_composition_write(c: &mut Criterion) { // Generate cache key using extractors let extractors = create_extractors(); let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, cache_key) = rt.block_on(async { extractors.get(request).await.into_cache_key() }); + let (_, cache_key) = rt.block_on(async { + extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); // Generate cacheable response let response = CacheableHttpResponse::from_response(create_reference_response()); @@ -671,7 +692,11 @@ fn bench_compare_request_predicates(c: &mut Criterion) { group.bench_function("static", |b| { b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(static_predicates.check(request).await) + std::hint::black_box( + static_predicates + .check(request, &mut EvalContext::new()) + .await, + ) }); }); @@ -679,7 +704,7 @@ fn bench_compare_request_predicates(c: &mut Criterion) { let predicates = dynamic_predicates.clone(); b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(predicates.check(request).await) + std::hint::black_box(predicates.check(request, &mut EvalContext::new()).await) }); }); @@ -705,7 +730,11 @@ fn bench_compare_response_predicates(c: &mut Criterion) { group.bench_function("static", |b| { b.to_async(&rt).iter(|| async { let response = CacheableHttpResponse::from_response(create_reference_response()); - std::hint::black_box(static_predicates.check(response).await) + std::hint::black_box( + static_predicates + .check(response, &mut EvalContext::new()) + .await, + ) }); }); @@ -713,7 +742,7 @@ fn bench_compare_response_predicates(c: &mut Criterion) { let predicates = dynamic_predicates.clone(); b.to_async(&rt).iter(|| async { let response = CacheableHttpResponse::from_response(create_reference_response()); - std::hint::black_box(predicates.check(response).await) + std::hint::black_box(predicates.check(response, &mut EvalContext::new()).await) }); }); @@ -741,7 +770,11 @@ fn bench_compare_extractors(c: &mut Criterion) { group.bench_function("static", |b| { b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(static_extractors.get(request).await) + std::hint::black_box( + static_extractors + .get(request, &mut EvalContext::new()) + .await, + ) }); }); @@ -749,7 +782,7 @@ fn bench_compare_extractors(c: &mut Criterion) { let extractors = dynamic_extractors.clone(); b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(extractors.get(request).await) + std::hint::black_box(extractors.get(request, &mut EvalContext::new()).await) }); }); @@ -784,8 +817,12 @@ fn bench_compare_cache_future_hit(c: &mut Criterion) { // Pre-populate static cache let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, static_cache_key) = - rt.block_on(async { static_extractors.get(request).await.into_cache_key() }); + let (_, static_cache_key) = rt.block_on(async { + static_extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); let response = CacheableHttpResponse::from_response(create_reference_response()); let cached_response = rt.block_on(async { @@ -818,8 +855,13 @@ fn bench_compare_cache_future_hit(c: &mut Criterion) { // Pre-populate dynamic cache let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, dyn_cache_key) = - rt.block_on(async { endpoint.extractors.get(request).await.into_cache_key() }); + let (_, dyn_cache_key) = rt.block_on(async { + endpoint + .extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); let response = CacheableHttpResponse::from_response(create_reference_response()); let cached_response = rt.block_on(async { @@ -1054,7 +1096,11 @@ fn bench_compare_body_request_predicates(c: &mut Criterion) { group.bench_function("static", |b| { b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(static_predicates.check(request).await) + std::hint::black_box( + static_predicates + .check(request, &mut EvalContext::new()) + .await, + ) }); }); @@ -1062,7 +1108,7 @@ fn bench_compare_body_request_predicates(c: &mut Criterion) { let predicates = dynamic_predicates.clone(); b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(predicates.check(request).await) + std::hint::black_box(predicates.check(request, &mut EvalContext::new()).await) }); }); @@ -1088,7 +1134,11 @@ fn bench_compare_body_response_predicates(c: &mut Criterion) { group.bench_function("static", |b| { b.to_async(&rt).iter(|| async { let response = CacheableHttpResponse::from_response(create_reference_response()); - std::hint::black_box(static_predicates.check(response).await) + std::hint::black_box( + static_predicates + .check(response, &mut EvalContext::new()) + .await, + ) }); }); @@ -1096,7 +1146,7 @@ fn bench_compare_body_response_predicates(c: &mut Criterion) { let predicates = dynamic_predicates.clone(); b.to_async(&rt).iter(|| async { let response = CacheableHttpResponse::from_response(create_reference_response()); - std::hint::black_box(predicates.check(response).await) + std::hint::black_box(predicates.check(response, &mut EvalContext::new()).await) }); }); @@ -1124,7 +1174,11 @@ fn bench_compare_body_extractors(c: &mut Criterion) { group.bench_function("static", |b| { b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(static_extractors.get(request).await) + std::hint::black_box( + static_extractors + .get(request, &mut EvalContext::new()) + .await, + ) }); }); @@ -1132,7 +1186,7 @@ fn bench_compare_body_extractors(c: &mut Criterion) { let extractors = dynamic_extractors.clone(); b.to_async(&rt).iter(|| async { let request = CacheableHttpRequest::from_request(create_reference_request()); - std::hint::black_box(extractors.get(request).await) + std::hint::black_box(extractors.get(request, &mut EvalContext::new()).await) }); }); @@ -1167,8 +1221,12 @@ fn bench_compare_body_cache_future_hit(c: &mut Criterion) { // Pre-populate static cache let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, static_cache_key) = - rt.block_on(async { static_extractors.get(request).await.into_cache_key() }); + let (_, static_cache_key) = rt.block_on(async { + static_extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); let response = CacheableHttpResponse::from_response(create_reference_response()); let cached_response = rt.block_on(async { @@ -1201,8 +1259,13 @@ fn bench_compare_body_cache_future_hit(c: &mut Criterion) { // Pre-populate dynamic cache let request = CacheableHttpRequest::from_request(create_reference_request()); - let (_, dyn_cache_key) = - rt.block_on(async { endpoint.extractors.get(request).await.into_cache_key() }); + let (_, dyn_cache_key) = rt.block_on(async { + endpoint + .extractors + .get(request, &mut EvalContext::new()) + .await + .into_cache_key() + }); let response = CacheableHttpResponse::from_response(create_reference_response()); let cached_response = rt.block_on(async { diff --git a/hitbox-test/src/fsm/world.rs b/hitbox-test/src/fsm/world.rs index ebc41d5b..5dc26bbb 100644 --- a/hitbox-test/src/fsm/world.rs +++ b/hitbox-test/src/fsm/world.rs @@ -14,8 +14,8 @@ use hitbox_backend::composition::policy::RefillPolicy; use hitbox_backend::{CacheBackend, CompositionBackend}; use hitbox_core::{ CacheKey, CachePolicy, CacheValue, CacheablePolicyData, CacheableRequest, CacheableResponse, - EntityPolicyConfig, Extractor, KeyPart, KeyParts, Offload, Predicate, PredicateResult, - RequestCachePolicy, ResponseCachePolicy, SmolStr, Upstream, + EntityPolicyConfig, EvalContext, Extractor, KeyPart, KeyParts, Offload, Predicate, + PredicateResult, RequestCachePolicy, ResponseCachePolicy, SmolStr, Upstream, }; use hitbox_moka::MokaBackend; @@ -47,9 +47,10 @@ impl CacheableRequest for SimpleRequest { E: Extractor + Send + Sync + 'a, { Box::pin(async move { - match predicates.check(self).await { + let mut ctx = EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(request) => { - let key_parts = extractors.get(request).await; + let key_parts = extractors.get(request, &mut ctx).await; let (request, cache_key) = key_parts.into_cache_key(); RequestCachePolicy::Cacheable(CacheablePolicyData { key: cache_key, @@ -79,7 +80,8 @@ impl CacheableResponse for SimpleResponse { where P: Predicate + Send + Sync, { - match predicates.check(self).await { + let mut ctx = EvalContext::new(); + match predicates.check(self, &mut ctx).await { PredicateResult::Cacheable(response) => { CachePolicy::Cacheable(CacheValue::new(response.0, None, None)) } @@ -109,7 +111,11 @@ pub struct ConfigurableRequestPredicate { impl Predicate for ConfigurableRequestPredicate { type Subject = SimpleRequest; - async fn check(&self, subject: Self::Subject) -> PredicateResult { + async fn check( + &self, + subject: Self::Subject, + _ctx: &mut EvalContext, + ) -> PredicateResult { if self.cacheable { PredicateResult::Cacheable(subject) } else { @@ -127,7 +133,11 @@ pub struct ConfigurableResponsePredicate { impl Predicate for ConfigurableResponsePredicate { type Subject = SimpleResponse; - async fn check(&self, subject: Self::Subject) -> PredicateResult { + async fn check( + &self, + subject: Self::Subject, + _ctx: &mut EvalContext, + ) -> PredicateResult { if self.cacheable { PredicateResult::Cacheable(subject) } else { @@ -147,7 +157,7 @@ pub struct FixedKeyExtractor; impl Extractor for FixedKeyExtractor { type Subject = SimpleRequest; - async fn get(&self, subject: Self::Subject) -> KeyParts { + async fn get(&self, subject: Self::Subject, _ctx: &mut EvalContext) -> KeyParts { let mut key_parts = KeyParts::new(subject); key_parts.push(KeyPart::new("fixed_key", Some("value"))); key_parts diff --git a/hitbox/CHANGELOG.md b/hitbox/CHANGELOG.md index f80b2ddd..8cdd9728 100644 --- a/hitbox/CHANGELOG.md +++ b/hitbox/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `Config` now implements `CacheConfigs` via `slice::from_ref` ([#253](https://github.com/hit-box/hitbox/pull/253)) - `Config` now stores `policy` as `Arc` ([#253](https://github.com/hit-box/hitbox/pull/253)) +- FSM now threads `&mut EvalContext` through predicate and extractor evaluation ### Changed - **Breaking:** Relaxed FSM lifetime bounds from `'static` to `'offload` for `Req`, `ReqP`, and `E` — enables caching of request types containing references ([#206](https://github.com/hit-box/hitbox/pull/206)) diff --git a/hitbox/src/config.rs b/hitbox/src/config.rs index ed579f3e..86e78325 100644 --- a/hitbox/src/config.rs +++ b/hitbox/src/config.rs @@ -35,7 +35,7 @@ pub type BoxExtractor = Box + Send + Sync>; /// # #[async_trait::async_trait] /// # impl Extractor for FixedKeyExtractor { /// # type Subject = String; -/// # async fn get(&self, subject: Self::Subject) -> KeyParts { +/// # async fn get(&self, subject: Self::Subject, _ctx: &mut hitbox::EvalContext) -> KeyParts { /// # KeyParts::new(subject) /// # } /// # } diff --git a/hitbox/src/fsm/selective/mod.rs b/hitbox/src/fsm/selective/mod.rs index ad889dd3..8676d4df 100644 --- a/hitbox/src/fsm/selective/mod.rs +++ b/hitbox/src/fsm/selective/mod.rs @@ -153,7 +153,11 @@ where match first_enabled { Some(idx) => { let pred = configs.configs()[idx].request_predicates(); - let predicate_future = Box::pin(async move { pred.check(request).await }); + let predicate_future = Box::pin(async move { + let mut ctx = hitbox_core::EvalContext::new(); + let result = pred.check(request, &mut ctx).await; + (result, ctx) + }); trace!(parent: &span, config_index = idx, "Checking first enabled config"); SelectiveCacheFuture { configs, @@ -214,10 +218,15 @@ where } => { let check = state.as_ref().expect(POLL_AFTER_READY); trace!(parent: &check.span, "FSM state: CheckPredicate"); - let result = ready!(predicate_future.poll(cx)); + let (result, ctx) = ready!(predicate_future.poll(cx)); let check = state.take().expect(POLL_AFTER_READY); check - .transition::<_, Res, _, _>(result, &*this.configs, &mut *this.upstream) + .transition::<_, Res, _, _>( + result, + ctx, + &*this.configs, + &mut *this.upstream, + ) .into_state(&*this.span) } SelectiveStateProj::ExtractKey { diff --git a/hitbox/src/fsm/selective/states.rs b/hitbox/src/fsm/selective/states.rs index ee839e61..c9e29bf8 100644 --- a/hitbox/src/fsm/selective/states.rs +++ b/hitbox/src/fsm/selective/states.rs @@ -10,8 +10,8 @@ use std::sync::Arc; use futures::future::BoxFuture; use hitbox_core::{ - CacheConfigs, Cacheable, Extractor as _, KeyParts, Offload, Predicate as _, PredicateResult, - Upstream, + CacheConfigs, Cacheable, EvalContext, Extractor as _, KeyParts, Offload, Predicate as _, + PredicateResult, Upstream, }; use pin_project::pin_project; use tracing::{Level, Span, debug, field, span, trace}; @@ -36,7 +36,7 @@ pub enum SelectiveState<'a, Inner, Req, UF> { /// Checking if current config's request predicates match. CheckPredicate { #[pin] - predicate_future: BoxFuture<'a, PredicateResult>, + predicate_future: BoxFuture<'a, (PredicateResult, EvalContext)>, state: Option, }, /// Matched config — extracting cache key. @@ -97,6 +97,7 @@ impl CheckPredicate { pub fn transition<'a, Req, Res, CC, U>( self, result: PredicateResult, + mut ctx: EvalContext, configs: &CC, upstream: &mut Option, ) -> CheckPredicateTransition<'a, Req, U::Future> @@ -115,7 +116,7 @@ impl CheckPredicate { "Config matched, extracting cache key" ); let ext = configs.configs()[self.config_index].extractors(); - let extract_future = Box::pin(async move { ext.get(request).await }); + let extract_future = Box::pin(async move { ext.get(request, &mut ctx).await }); CheckPredicateTransition::ExtractKey { extract_future, config_index: self.config_index, @@ -139,7 +140,10 @@ impl CheckPredicate { match next { Some(next_idx) => { let pred = configs.configs()[next_idx].request_predicates(); - let predicate_future = Box::pin(async move { pred.check(request).await }); + let predicate_future = Box::pin(async move { + let result = pred.check(request, &mut ctx).await; + (result, ctx) + }); CheckPredicateTransition::NextConfig { predicate_future, config_index: next_idx, diff --git a/hitbox/src/fsm/selective/transitions.rs b/hitbox/src/fsm/selective/transitions.rs index 70c9e645..ec513a11 100644 --- a/hitbox/src/fsm/selective/transitions.rs +++ b/hitbox/src/fsm/selective/transitions.rs @@ -6,7 +6,7 @@ #![allow(missing_docs)] use futures::future::BoxFuture; -use hitbox_core::{KeyParts, PredicateResult}; +use hitbox_core::{EvalContext, KeyParts, PredicateResult}; use tracing::Span; use super::states::{CheckPredicate, ExtractKey, Passthrough, SelectiveState}; @@ -24,7 +24,7 @@ pub enum CheckPredicateTransition<'a, Req, UF> { }, /// Request did not match — try the next enabled config. NextConfig { - predicate_future: BoxFuture<'a, PredicateResult>, + predicate_future: BoxFuture<'a, (PredicateResult, EvalContext)>, config_index: usize, }, /// No more configs to try — pass through to upstream. diff --git a/hitbox/src/lib.rs b/hitbox/src/lib.rs index e5155de8..4d7f042d 100644 --- a/hitbox/src/lib.rs +++ b/hitbox/src/lib.rs @@ -44,8 +44,8 @@ pub use error::CacheError; pub use hitbox_core::{ And, BackendLabel, CacheKey, CachePolicy, CacheState, CacheValue, Cacheable, CacheablePolicyData, CacheableRequest, CacheableResponse, DisabledOffload, EntityPolicyConfig, - Extractor, KeyPart, KeyParts, Neutral, Not, Offload, OffloadKey, Or, Predicate, PredicateExt, - Raw, RequestCachePolicy, ResponseCachePolicy, Upstream, + EvalContext, Extractor, KeyPart, KeyParts, Neutral, Not, Offload, OffloadKey, Or, Predicate, + PredicateExt, Raw, RequestCachePolicy, ResponseCachePolicy, Upstream, }; /// Hidden re-export of serde for derive macro generated code.