Skip to main content

hitbox_core/
extractor.rs

1//! Cache key extraction from requests.
2//!
3//! This module provides the [`Extractor`] trait for extracting data from
4//! requests to build cache keys.
5//!
6//! ## Overview
7//!
8//! Extractors pull relevant data from requests (like HTTP method, path,
9//! query parameters) and produce [`KeyParts`] that form the cache key.
10//! Multiple extractors can be chained to build complex cache keys.
11//!
12//! ## Composability
13//!
14//! Extractors are designed to be composed. Protocol-specific crates like
15//! `hitbox-http` provide extractors for common request components that
16//! can be combined to create precise cache keys.
17//!
18//! ## Example
19//!
20//! ```ignore
21//! use hitbox_core::{Extractor, KeyParts, KeyPart};
22//!
23//! #[derive(Debug)]
24//! struct MethodExtractor;
25//!
26//! #[async_trait::async_trait]
27//! impl Extractor for MethodExtractor {
28//!     type Subject = HttpRequest;
29//!
30//!     async fn get(&self, request: Self::Subject) -> KeyParts<Self::Subject> {
31//!         let mut parts = KeyParts::new(request);
32//!         parts.push(KeyPart::new("method", Some(request.method().as_str())));
33//!         parts
34//!     }
35//! }
36//! ```
37
38use std::sync::Arc;
39
40use async_trait::async_trait;
41
42use crate::KeyParts;
43
44/// Trait for extracting cache key components from a subject.
45///
46/// Extractors are the mechanism for building cache keys from requests.
47/// They are **protocol-agnostic** - the same trait works for HTTP requests,
48/// gRPC messages, or any other protocol type.
49///
50/// # Type Parameters
51///
52/// The `Subject` associated type defines what this extractor processes.
53/// Typically this is a request type from which cache key components
54/// are extracted.
55///
56/// # Ownership
57///
58/// The `get` method takes ownership of the subject and returns it wrapped
59/// in [`KeyParts`] along with the extracted key components. This allows
60/// extractors to be chained without cloning.
61///
62/// # Blanket Implementations
63///
64/// This trait is implemented for:
65/// - `&T` where `T: Extractor`
66/// - `Box<T>` where `T: Extractor`
67/// - `Arc<T>` where `T: Extractor`
68#[async_trait]
69pub trait Extractor {
70    /// The type from which cache key components are extracted.
71    type Subject;
72
73    /// Extract cache key components from the subject.
74    ///
75    /// Returns a [`KeyParts`] containing the subject and accumulated key parts.
76    async fn get(&self, subject: Self::Subject) -> KeyParts<Self::Subject>;
77}
78
79#[async_trait]
80impl<T> Extractor for &T
81where
82    T: Extractor + ?Sized + Sync,
83    T::Subject: Send,
84{
85    type Subject = T::Subject;
86
87    async fn get(&self, subject: T::Subject) -> KeyParts<T::Subject> {
88        self.get(subject).await
89    }
90}
91
92#[async_trait]
93impl<T> Extractor for Box<T>
94where
95    T: Extractor + ?Sized + Sync,
96    T::Subject: Send,
97{
98    type Subject = T::Subject;
99
100    async fn get(&self, subject: T::Subject) -> KeyParts<T::Subject> {
101        self.as_ref().get(subject).await
102    }
103}
104
105#[async_trait]
106impl<T> Extractor for Arc<T>
107where
108    T: Extractor + Send + Sync + ?Sized,
109    T::Subject: Send,
110{
111    type Subject = T::Subject;
112
113    async fn get(&self, subject: T::Subject) -> KeyParts<T::Subject> {
114        self.as_ref().get(subject).await
115    }
116}