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}