micro_web/router/
filter.rs

1//! Request filtering module that provides composable request filters.
2//!
3//! This module implements a filter system that allows you to:
4//! - Filter requests based on HTTP methods
5//! - Filter requests based on headers
6//! - Combine multiple filters using AND/OR logic
7//! - Create custom filters using closures
8//!
9//! ## Thread Safety
10//!
11//! All filters must implement the `Filter` trait, which requires `Send + Sync`.
12//! This ensures that filters can be safely shared and used across threads,
13//! which is essential for concurrent request handling in a web server environment.
14//!
15//! # Examples
16//!
17//! ```
18//! use micro_web::router::filter::{all_filter, any_filter, get_method, header};
19//!
20//! // Create a filter that matches GET requests
21//! let get_filter = get_method();
22//!
23//! // Create a filter that checks for specific header
24//! let auth_filter = header("Authorization", "Bearer token");
25//!
26//! // Combine filters with AND logic
27//! let mut combined = all_filter();
28//! combined.and(get_filter).and(auth_filter);
29//! ```
30
31use crate::RequestContext;
32use http::{HeaderName, HeaderValue, Method};
33
34/// Core trait for request filtering.
35///
36/// Implementors of this trait can be used to filter HTTP requests
37/// based on custom logic. Filters can be composed using [`AllFilter`]
38/// and [`AnyFilter`].
39///
40///
41/// The `Filter` trait requires `Send + Sync`, ensuring that filters
42/// can be safely used in a multithreaded environment.
43pub trait Filter: Send + Sync {
44    /// Check if the request matches this filter's criteria.
45    ///
46    /// Returns `true` if the request should be allowed, `false` otherwise.
47    fn matches(&self, req: &RequestContext) -> bool;
48}
49
50/// A filter that wraps a closure.
51struct FnFilter<F: Fn(&RequestContext) -> bool>(F);
52
53impl<F: Fn(&RequestContext) -> bool + Send + Sync> Filter for FnFilter<F> {
54    fn matches(&self, req: &RequestContext) -> bool {
55        self.0(req)
56    }
57}
58
59/// Creates a new filter from a closure.
60///
61/// This allows creating custom filters using simple closures.
62///
63/// # Example
64/// ```
65/// use micro_web::router::filter::filter_fn;
66///
67/// let custom_filter = filter_fn(|req| {
68///     req.uri().path().starts_with("/api")
69/// });
70/// ```
71pub fn filter_fn<F>(f: F) -> impl Filter
72where
73    F: Fn(&RequestContext) -> bool + Send + Sync,
74{
75    FnFilter(f)
76}
77
78/// Creates a filter that always returns true.
79#[inline(always)]
80pub const fn true_filter() -> TrueFilter {
81    TrueFilter
82}
83
84/// Creates a filter that always returns false.
85#[inline(always)]
86pub const fn false_filter() -> FalseFilter {
87    FalseFilter
88}
89
90/// A filter that always returns true.
91pub struct TrueFilter;
92impl Filter for TrueFilter {
93    #[inline(always)]
94    fn matches(&self, _req: &RequestContext) -> bool {
95        true
96    }
97}
98
99/// A filter that always returns false.
100pub struct FalseFilter;
101impl Filter for FalseFilter {
102    #[inline(always)]
103    fn matches(&self, _req: &RequestContext) -> bool {
104        false
105    }
106}
107
108/// Creates a new OR-composed filter chain.
109pub fn any_filter() -> AnyFilter {
110    AnyFilter::new()
111}
112
113/// Compose filters with OR logic.
114///
115/// If any inner filter succeeds, the whole filter succeeds.
116/// An empty filter chain returns true by default.
117pub struct AnyFilter {
118    filters: Vec<Box<dyn Filter>>,
119}
120
121impl AnyFilter {
122    fn new() -> Self {
123        Self { filters: vec![] }
124    }
125
126    /// Add a new filter to the OR chain.
127    pub fn or<F: Filter + 'static>(&mut self, filter: F) -> &mut Self {
128        self.filters.push(Box::new(filter));
129        self
130    }
131}
132
133impl Filter for AnyFilter {
134    fn matches(&self, req: &RequestContext) -> bool {
135        if self.filters.is_empty() {
136            return true;
137        }
138
139        for filter in &self.filters {
140            if filter.matches(req) {
141                return true;
142            }
143        }
144
145        false
146    }
147}
148
149/// Creates a new AND-composed filter chain.
150pub fn all_filter() -> AllFilter {
151    AllFilter::new()
152}
153
154/// Compose filters with AND logic.
155///
156/// All inner filters must succeed for the whole filter to succeed.
157/// An empty filter chain returns true by default.
158pub struct AllFilter {
159    filters: Vec<Box<dyn Filter>>,
160}
161
162impl AllFilter {
163    fn new() -> Self {
164        Self { filters: vec![] }
165    }
166
167    /// Add a new filter to the AND chain.
168    pub fn and<F: Filter + 'static>(&mut self, filter: F) -> &mut Self {
169        self.filters.push(Box::new(filter));
170        self
171    }
172}
173
174impl Filter for AllFilter {
175    fn matches(&self, req: &RequestContext) -> bool {
176        if self.filters.is_empty() {
177            return true;
178        }
179
180        for filter in &self.filters {
181            if !filter.matches(req) {
182                return false;
183            }
184        }
185
186        true
187    }
188}
189
190/// A filter that matches HTTP methods.
191pub struct MethodFilter(Method);
192
193impl Filter for MethodFilter {
194    fn matches(&self, req: &RequestContext) -> bool {
195        self.0.eq(req.method())
196    }
197}
198
199macro_rules! method_filter {
200    ($method:ident, $upper_case_method:ident) => {
201        #[doc = concat!("Creates a filter that matches HTTP ", stringify!($upper_case_method), " requests.")]
202        #[inline]
203        pub fn $method() -> MethodFilter {
204            MethodFilter(Method::$upper_case_method)
205        }
206    };
207}
208
209method_filter!(get_method, GET);
210method_filter!(post_method, POST);
211method_filter!(put_method, PUT);
212method_filter!(delete_method, DELETE);
213method_filter!(head_method, HEAD);
214method_filter!(options_method, OPTIONS);
215method_filter!(connect_method, CONNECT);
216method_filter!(patch_method, PATCH);
217method_filter!(trace_method, TRACE);
218
219/// Creates a filter that matches a specific header name and value.
220#[inline]
221pub fn header<K, V>(header_name: K, header_value: V) -> HeaderFilter
222where
223    HeaderName: TryFrom<K>,
224    <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
225    HeaderValue: TryFrom<V>,
226    <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
227{
228    // TODO: need to process the unwrap
229    let name = <HeaderName as TryFrom<K>>::try_from(header_name).map_err(Into::into).unwrap();
230    let value = <HeaderValue as TryFrom<V>>::try_from(header_value).map_err(Into::into).unwrap();
231    HeaderFilter(name, value)
232}
233
234/// A filter that matches HTTP headers.
235pub struct HeaderFilter(HeaderName, HeaderValue);
236
237impl Filter for HeaderFilter {
238    fn matches(&self, req: &RequestContext) -> bool {
239        let value_option = req.headers().get(&self.0);
240        value_option.map(|value| self.1.eq(value)).unwrap_or(false)
241    }
242}