hitbox_http/predicates/request/method.rs
1use crate::CacheableHttpRequest;
2use async_trait::async_trait;
3use hitbox::Neutral;
4use hitbox::predicate::{Predicate, PredicateResult};
5
6/// Matching operations for HTTP methods.
7#[derive(Debug)]
8pub enum Operation {
9 /// Match a single HTTP method.
10 Eq(http::Method),
11 /// Match any of the specified HTTP methods.
12 In(Vec<http::Method>),
13}
14
15/// A predicate that matches requests by HTTP method.
16///
17/// # Type Parameters
18///
19/// * `P` - The inner predicate to chain with. Use [`Method::new`] to start
20/// a new predicate chain (uses [`Neutral`] internally), or use the
21/// [`MethodPredicate`] extension trait to chain onto an existing predicate.
22///
23/// # Examples
24///
25/// Match only GET requests:
26///
27/// ```
28/// use hitbox_http::predicates::request::Method;
29///
30/// # use bytes::Bytes;
31/// # use http_body_util::Empty;
32/// # use hitbox::Neutral;
33/// # use hitbox_http::CacheableHttpRequest;
34/// # type Subject = CacheableHttpRequest<Empty<Bytes>>;
35/// let predicate = Method::new(http::Method::GET).unwrap();
36/// # let _: &Method<Neutral<Subject>> = &predicate;
37/// ```
38///
39/// Match GET or HEAD requests (using the builder pattern):
40///
41/// ```
42/// use hitbox::Neutral;
43/// use hitbox_http::predicates::request::Method;
44///
45/// # use bytes::Bytes;
46/// # use http_body_util::Empty;
47/// # use hitbox_http::CacheableHttpRequest;
48/// # type Subject = CacheableHttpRequest<Empty<Bytes>>;
49/// let predicate = Method::new_in(
50/// Neutral::new(),
51/// vec![http::Method::GET, http::Method::HEAD],
52/// );
53/// # let _: &Method<Neutral<Subject>> = &predicate;
54/// ```
55#[derive(Debug)]
56pub struct Method<P> {
57 operation: Operation,
58 inner: P,
59}
60
61impl<S> Method<Neutral<S>> {
62 /// Creates a predicate matching requests with the specified HTTP method.
63 ///
64 /// Returns [`Cacheable`](hitbox::predicate::PredicateResult::Cacheable) when
65 /// the request method matches, [`NonCacheable`](hitbox::predicate::PredicateResult::NonCacheable) otherwise.
66 ///
67 /// # Errors
68 ///
69 /// Returns an error if `method` cannot be converted to [`http::Method`].
70 /// When passing `http::Method` directly, this is infallible.
71 /// When passing a string, returns [`http::method::InvalidMethod`] if the
72 /// string is not a valid HTTP method.
73 pub fn new<E, T>(method: T) -> Result<Self, E>
74 where
75 T: TryInto<http::Method, Error = E>,
76 {
77 Ok(Method {
78 operation: Operation::Eq(method.try_into()?),
79 inner: Neutral::new(),
80 })
81 }
82}
83
84impl<P> Method<P> {
85 /// Creates a predicate matching requests with any of the specified HTTP methods.
86 ///
87 /// Returns [`Cacheable`](hitbox::predicate::PredicateResult::Cacheable) when
88 /// the request method is in the provided list, [`NonCacheable`](hitbox::predicate::PredicateResult::NonCacheable) otherwise.
89 ///
90 /// Use this for caching strategies that apply to multiple methods (e.g., GET and HEAD).
91 pub fn new_in(inner: P, methods: Vec<http::Method>) -> Self {
92 Method {
93 operation: Operation::In(methods),
94 inner,
95 }
96 }
97}
98
99/// Extension trait for adding method matching to a predicate chain.
100///
101/// # For Callers
102///
103/// Chain this to match requests by their HTTP method. Use with specific
104/// methods like `http::Method::GET` or `http::Method::POST`.
105///
106/// # For Implementors
107///
108/// This trait is automatically implemented for all [`Predicate`]
109/// types. You don't need to implement it manually.
110pub trait MethodPredicate: Sized {
111 /// Adds an HTTP method match to this predicate chain.
112 fn method(self, method: http::Method) -> Method<Self>;
113}
114
115impl<P> MethodPredicate for P
116where
117 P: Predicate,
118{
119 fn method(self, method: http::Method) -> Method<Self> {
120 Method {
121 operation: Operation::Eq(method),
122 inner: self,
123 }
124 }
125}
126
127#[async_trait]
128impl<P, ReqBody> Predicate for Method<P>
129where
130 P: Predicate<Subject = CacheableHttpRequest<ReqBody>> + Send + Sync,
131 ReqBody: hyper::body::Body + Send + 'static,
132 ReqBody::Error: Send,
133{
134 type Subject = P::Subject;
135
136 async fn check(&self, request: Self::Subject) -> PredicateResult<Self::Subject> {
137 match self.inner.check(request).await {
138 PredicateResult::Cacheable(request) => {
139 let is_cacheable = match &self.operation {
140 Operation::Eq(method) => *method == request.parts().method,
141 Operation::In(methods) => methods.contains(&request.parts().method),
142 };
143 if is_cacheable {
144 PredicateResult::Cacheable(request)
145 } else {
146 PredicateResult::NonCacheable(request)
147 }
148 }
149 PredicateResult::NonCacheable(request) => PredicateResult::NonCacheable(request),
150 }
151 }
152}