Skip to main content

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}