rama_core/matcher/
mod.rs

1//! matcher utilities for any middleware where need to match
2//! on incoming requests within a given [`Context`]
3//!
4//! This module provides the [`Matcher`] trait and convenience utilities around it.
5//!
6//! - Examples of this are iterator "reducers" as made available via [`IteratorMatcherExt`],
7//!   as well as optional [`Matcher::or`] and [`Matcher::and`] trait methods.
8//! - These all serve as building blocks together with [`And`], [`Or`], [`Not`] and a bool
9//!   to combine and transform any kind of [`Matcher`].
10//! - And finally there is [`MatchFn`], easily created using [`match_fn`] to create a [`Matcher`]
11//!   from any compatible [`Fn`].
12//!
13//! [`Context`]: crate::Context
14
15use std::sync::Arc;
16
17use super::{Context, context::Extensions};
18use crate::Service;
19use rama_macros::paste;
20use rama_utils::macros::all_the_tuples_no_last_special_case;
21
22mod op_or;
23#[doc(inline)]
24pub use op_or::Or;
25
26mod op_and;
27#[doc(inline)]
28pub use op_and::And;
29
30mod op_not;
31#[doc(inline)]
32pub use op_not::Not;
33
34mod mfn;
35#[doc(inline)]
36pub use mfn::{MatchFn, match_fn};
37
38mod iter;
39#[doc(inline)]
40pub use iter::IteratorMatcherExt;
41
42mod ext;
43#[doc(inline)]
44pub use ext::ExtensionMatcher;
45
46/// A condition to decide whether `Request` within the given [`Context`] matches for
47/// router or other middleware purposes.
48pub trait Matcher<State, Request>: Send + Sync + 'static {
49    /// returns true on a match, false otherwise
50    ///
51    /// `ext` is None in case the callee is not interested in collecting potential
52    /// match metadata gathered during the matching process. An example of this
53    /// path parameters for an http Uri matcher.
54    fn matches(&self, ext: Option<&mut Extensions>, ctx: &Context<State>, req: &Request) -> bool;
55
56    /// Provide an alternative matcher to match if the current one does not match.
57    fn or<M>(self, other: M) -> impl Matcher<State, Request>
58    where
59        Self: Sized,
60        M: Matcher<State, Request>,
61    {
62        Or::new((self, other))
63    }
64
65    /// Add another condition to match on top of the current one.
66    fn and<M>(self, other: M) -> impl Matcher<State, Request>
67    where
68        Self: Sized,
69        M: Matcher<State, Request>,
70    {
71        And::new((self, other))
72    }
73
74    /// Negate the current condition.
75    fn not(self) -> impl Matcher<State, Request>
76    where
77        Self: Sized,
78    {
79        Not::new(self)
80    }
81}
82
83impl<State, Request, T> Matcher<State, Request> for Arc<T>
84where
85    T: Matcher<State, Request>,
86{
87    fn matches(&self, ext: Option<&mut Extensions>, ctx: &Context<State>, req: &Request) -> bool {
88        (**self).matches(ext, ctx, req)
89    }
90}
91
92impl<State, Request, T> Matcher<State, Request> for &'static T
93where
94    T: Matcher<State, Request>,
95{
96    fn matches(&self, ext: Option<&mut Extensions>, ctx: &Context<State>, req: &Request) -> bool {
97        (**self).matches(ext, ctx, req)
98    }
99}
100
101impl<State, Request, T> Matcher<State, Request> for Option<T>
102where
103    T: Matcher<State, Request>,
104{
105    fn matches(&self, ext: Option<&mut Extensions>, ctx: &Context<State>, req: &Request) -> bool {
106        match self {
107            Some(inner) => inner.matches(ext, ctx, req),
108            None => false,
109        }
110    }
111}
112
113impl<State, Request, T> Matcher<State, Request> for Box<T>
114where
115    T: Matcher<State, Request>,
116{
117    fn matches(&self, ext: Option<&mut Extensions>, ctx: &Context<State>, req: &Request) -> bool {
118        (**self).matches(ext, ctx, req)
119    }
120}
121
122impl<State, Request> Matcher<State, Request> for Box<(dyn Matcher<State, Request> + 'static)>
123where
124    State: Clone + Send + Sync + 'static,
125    Request: Send + 'static,
126{
127    fn matches(&self, ext: Option<&mut Extensions>, ctx: &Context<State>, req: &Request) -> bool {
128        (**self).matches(ext, ctx, req)
129    }
130}
131
132impl<State, Request> Matcher<State, Request> for bool {
133    fn matches(&self, _: Option<&mut Extensions>, _: &Context<State>, _: &Request) -> bool {
134        *self
135    }
136}
137
138macro_rules! impl_matcher_either {
139    ($id:ident, $($param:ident),+ $(,)?) => {
140        impl<$($param),+, State, Request> Matcher<State, Request> for crate::combinators::$id<$($param),+>
141        where
142            $($param: Matcher<State, Request>),+,
143            Request: Send + 'static,
144            State: Clone + Send + Sync + 'static,
145        {
146            fn matches(
147                &self,
148                ext: Option<&mut Extensions>,
149                ctx: &Context<State>,
150                req: &Request
151            ) -> bool{
152                match self {
153                    $(
154                        crate::combinators::$id::$param(layer) => layer.matches(ext, ctx, req),
155                    )+
156                }
157            }
158        }
159    };
160}
161
162crate::combinators::impl_either!(impl_matcher_either);
163
164/// Wrapper type that can be used to turn a tuple of ([`Matcher`], [`Service`]) tuples
165/// into a single [`Service`].
166pub struct MatcherRouter<N>(pub N);
167
168impl<N: std::fmt::Debug> std::fmt::Debug for MatcherRouter<N> {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        f.debug_tuple("MatcherRouter").field(&self.0).finish()
171    }
172}
173
174impl<N: Clone> Clone for MatcherRouter<N> {
175    fn clone(&self) -> Self {
176        Self(self.0.clone())
177    }
178}
179
180macro_rules! impl_matcher_service_tuple {
181    ($($T:ident),+ $(,)?) => {
182        paste!{
183            #[allow(non_camel_case_types)]
184            #[allow(non_snake_case)]
185            impl<State, $([<M_ $T>], $T),+, S, Request, Response, Error> Service<State, Request> for MatcherRouter<($(([<M_ $T>], $T)),+, S)>
186            where
187                State: Clone + Send + Sync + 'static,
188                Request: Send + 'static,
189                Response: Send + 'static,
190                $(
191                    [<M_ $T>]: Matcher<State, Request>,
192                    $T: Service<State, Request, Response = Response, Error = Error>,
193                )+
194                S: Service<State, Request, Response = Response, Error = Error>,
195                Error: Send + 'static,
196            {
197                type Response = Response;
198                type Error = Error;
199
200                async fn serve(
201                    &self,
202                    mut ctx: Context<State>,
203                    req: Request,
204                ) -> Result<Self::Response, Self::Error> {
205                    let ($(([<M_ $T>], $T)),+, S) = &self.0;
206                    let mut ext = Extensions::new();
207                    $(
208                        if [<M_ $T>].matches(Some(&mut ext), &ctx, &req) {
209                            ctx.extend(ext);
210                            return $T.serve(ctx, req).await;
211                        }
212                        ext.clear();
213                    )+
214                    S.serve(ctx, req).await
215                }
216            }
217        }
218    };
219}
220
221all_the_tuples_no_last_special_case!(impl_matcher_service_tuple);
222
223#[cfg(test)]
224mod test;