Skip to main content

rama_core/matcher/
mod.rs

1//! Matcher utilities for any middleware where need to match
2//! on incoming inputs.
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
13use crate::std::{boxed::Box, sync::Arc};
14
15use super::extensions::Extensions;
16use crate::Service;
17use crate::extensions::ExtensionsRef;
18use rama_macros::paste;
19use rama_utils::macros::all_the_tuples_no_last_special_case;
20
21pub mod service;
22
23mod op_or;
24#[doc(inline)]
25pub use op_or::Or;
26
27mod op_and;
28#[doc(inline)]
29pub use op_and::And;
30
31mod op_not;
32#[doc(inline)]
33pub use op_not::Not;
34
35mod mfn;
36#[doc(inline)]
37pub use mfn::{MatchFn, match_fn};
38
39mod iter;
40#[doc(inline)]
41pub use iter::IteratorMatcherExt;
42
43mod ext;
44#[doc(inline)]
45pub use ext::ExtensionMatcher;
46
47/// A condition to decide whether `Input` matches for
48/// router or other middleware purposes.
49pub trait Matcher<Input>: Send + Sync + 'static {
50    /// returns true on a match, false otherwise
51    ///
52    /// `ext` is None in case the callee is not interested in collecting potential
53    /// match metadata gathered during the matching process. An example of this
54    /// path parameters for an http Uri matcher.
55    fn matches(&self, ext: Option<&Extensions>, input: &Input) -> bool;
56
57    /// Provide an alternative matcher to match if the current one does not match.
58    fn or<M>(self, other: M) -> impl Matcher<Input>
59    where
60        Self: Sized,
61        M: Matcher<Input>,
62    {
63        Or::new((self, other))
64    }
65
66    /// Add another condition to match on top of the current one.
67    fn and<M>(self, other: M) -> impl Matcher<Input>
68    where
69        Self: Sized,
70        M: Matcher<Input>,
71    {
72        And::new((self, other))
73    }
74
75    /// Negate the current condition.
76    fn not(self) -> impl Matcher<Input>
77    where
78        Self: Sized,
79    {
80        Not::new(self)
81    }
82}
83
84impl<Input, T> Matcher<Input> for Arc<T>
85where
86    T: Matcher<Input>,
87{
88    fn matches(&self, ext: Option<&Extensions>, input: &Input) -> bool {
89        (**self).matches(ext, input)
90    }
91}
92
93impl<Input, T> Matcher<Input> for &'static T
94where
95    T: Matcher<Input>,
96{
97    #[inline(always)]
98    fn matches(&self, ext: Option<&Extensions>, input: &Input) -> bool {
99        (**self).matches(ext, input)
100    }
101}
102
103impl<Input, T> Matcher<Input> for Option<T>
104where
105    T: Matcher<Input>,
106{
107    fn matches(&self, ext: Option<&Extensions>, input: &Input) -> bool {
108        match self {
109            Some(inner) => inner.matches(ext, input),
110            None => false,
111        }
112    }
113}
114
115impl<Input, T> Matcher<Input> for Box<T>
116where
117    T: Matcher<Input>,
118{
119    fn matches(&self, ext: Option<&Extensions>, input: &Input) -> bool {
120        (**self).matches(ext, input)
121    }
122}
123
124impl<Input> Matcher<Input> for Box<dyn Matcher<Input> + 'static>
125where
126    Input: Send + 'static,
127{
128    fn matches(&self, ext: Option<&Extensions>, input: &Input) -> bool {
129        (**self).matches(ext, input)
130    }
131}
132
133impl<Input> Matcher<Input> for bool {
134    fn matches(&self, _: Option<&Extensions>, _: &Input) -> bool {
135        *self
136    }
137}
138
139macro_rules! impl_matcher_either {
140    ($id:ident, $($param:ident),+ $(,)?) => {
141        impl<$($param),+, Input> Matcher<Input> for crate::combinators::$id<$($param),+>
142        where
143            $($param: Matcher<Input>),+,
144            Input: Send + 'static,
145
146        {
147            fn matches(
148                &self,
149                ext: Option<&Extensions>,
150                input: &Input
151            ) -> bool{
152                match self {
153                    $(
154                        crate::combinators::$id::$param(layer) => layer.matches(ext, input),
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`].
166#[derive(Debug, Clone)]
167pub struct MatcherRouter<N>(pub N);
168
169macro_rules! impl_matcher_service_tuple {
170    ($($T:ident),+ $(,)?) => {
171        paste!{
172            #[expect(non_camel_case_types)]
173            #[expect(non_snake_case)]
174            impl<$([<M_ $T>], $T),+, S, Input, Output, Error> Service<Input> for MatcherRouter<($(([<M_ $T>], $T)),+, S)>
175            where
176                Input: Send + ExtensionsRef + 'static,
177                Output: Send + 'static,
178                $(
179                    [<M_ $T>]: Matcher<Input>,
180                    $T: Service<Input, Output = Output, Error = Error>,
181                )+
182                S: Service<Input, Output = Output, Error = Error>,
183                Error: Send + 'static,
184            {
185                type Output = Output;
186                type Error = Error;
187
188                async fn serve(
189                    &self,
190                    input: Input,
191                ) -> Result<Self::Output, Self::Error> {
192                    let ($(([<M_ $T>], $T)),+, S) = &self.0;
193                    $(
194                        let ext = Extensions::new();
195                        if [<M_ $T>].matches(Some(&ext), &input) {
196                            input.extensions().extend(&ext);
197                            return $T.serve(input).await;
198                        }
199                    )+
200                    S.serve(input).await
201                }
202            }
203        }
204    };
205}
206
207all_the_tuples_no_last_special_case!(impl_matcher_service_tuple);
208
209#[cfg(test)]
210mod test;