hitbox_http/extractors/mod.rs
1//! Cache key extractors for HTTP requests.
2//!
3//! Extractors generate cache key parts from HTTP request components. They
4//! implement the [`Extractor`] trait and can be chained using the builder pattern.
5//!
6//! # Available Extractors
7//!
8//! | Extractor | Description |
9//! |-----------|-------------|
10//! | [`Method`] | Extract HTTP method (GET, POST, etc.) |
11//! | [`Path`] | Extract path parameters using patterns like `/users/{id}` |
12//! | [`header::Header`] | Extract header values |
13//! | [`query::Query`] | Extract query parameters |
14//! | [`body::Body`] | Extract from body (hash, JQ, regex) |
15//! | [`Version`] | Extract HTTP version |
16//!
17//! # Builder Pattern
18//!
19//! Start with [`Method::new()`] and chain other extractors:
20//!
21//! ```
22//! use hitbox_http::extractors::{Method, path::PathExtractor, query::QueryExtractor};
23//!
24//! # use bytes::Bytes;
25//! # use http_body_util::Empty;
26//! # use hitbox_http::extractors::{NeutralExtractor, Path, query::Query};
27//! let extractor = Method::new()
28//! .path("/users/{user_id}/posts/{post_id}")
29//! .query("page".to_string())
30//! .query("limit".to_string());
31//! # let _: &Query<Query<Path<Method<NeutralExtractor<Empty<Bytes>>>>>> = &extractor;
32//! ```
33//!
34//! # Cache Key Structure
35//!
36//! Each extractor adds [`KeyPart`]s to the cache key. A `KeyPart` has:
37//! - A name (e.g., "user_id", "page", "method")
38//! - An optional value (e.g., "42", "1", "GET")
39//!
40//! The final cache key is computed from all collected parts.
41//!
42//! # Transforms
43//!
44//! Header and query extractors support value transformations via [`transform::Transform`]:
45//! - `Hash`: SHA256 hash (truncated to 16 hex chars)
46//! - `Lowercase`: Convert to lowercase
47//! - `Uppercase`: Convert to uppercase
48//!
49//! [`Extractor`]: hitbox::Extractor
50//! [`KeyPart`]: hitbox::KeyPart
51
52use std::marker::PhantomData;
53
54use async_trait::async_trait;
55use hitbox::{Extractor, KeyParts};
56
57use crate::CacheableHttpRequest;
58
59pub use method::Method;
60pub use path::Path;
61pub use version::Version;
62
63pub mod body;
64pub mod header;
65/// HTTP method extraction for cache keys.
66pub mod method;
67/// Path parameter extraction for cache keys.
68pub mod path;
69pub mod query;
70pub mod transform;
71pub mod version;
72
73/// Base extractor that produces an empty cache key.
74///
75/// This is an internal building block used by other extractors. Users should
76/// start extractor chains with [`Method::new()`] instead.
77///
78/// # Type Parameters
79///
80/// * `ReqBody` - The HTTP request body type. Must implement [`hyper::body::Body`]
81/// with `Send` bounds. This parameter propagates through extractor chains
82/// to ensure type safety.
83///
84/// # When You'll Encounter This
85///
86/// You typically don't create this directly. It appears as the innermost type
87/// in extractor chains:
88///
89/// ```
90/// use hitbox_http::extractors::{Method, path::PathExtractor};
91///
92/// # use bytes::Bytes;
93/// # use http_body_util::Empty;
94/// # use hitbox_http::extractors::{NeutralExtractor, Path};
95/// // The full type is Path<Method<NeutralExtractor<Empty<Bytes>>>>
96/// let extractor = Method::new().path("/users/{id}");
97/// # let _: &Path<Method<NeutralExtractor<Empty<Bytes>>>> = &extractor;
98/// ```
99#[derive(Debug)]
100pub struct NeutralExtractor<ReqBody> {
101 _res: PhantomData<fn(ReqBody) -> ReqBody>,
102}
103
104impl<ResBody> NeutralExtractor<ResBody> {
105 /// Creates a new neutral extractor.
106 pub fn new() -> Self {
107 NeutralExtractor { _res: PhantomData }
108 }
109}
110
111#[async_trait]
112impl<ResBody> Extractor for NeutralExtractor<ResBody>
113where
114 ResBody: hyper::body::Body + Send + 'static,
115 ResBody::Error: Send,
116{
117 type Subject = CacheableHttpRequest<ResBody>;
118
119 async fn get(&self, subject: Self::Subject) -> KeyParts<Self::Subject> {
120 KeyParts::new(subject)
121 }
122}
123
124impl<ResBody> Default for NeutralExtractor<ResBody> {
125 fn default() -> Self {
126 Self::new()
127 }
128}