Skip to main content

dhttp_access/
matcher.rs

1#![allow(clippy::mutable_key_type)]
2
3use std::{collections::BTreeMap, fmt::Display, str::FromStr};
4
5use derive_more::{From, Into};
6use serde::{Deserialize, Serialize};
7use snafu::{OptionExt, ResultExt};
8
9use crate::{
10    action::RequestAction,
11    error::location::MatchLocationFailed,
12    expr::{
13        atomics::AtomicLocationRuleExpr, eval::Evaluable, exprs::LocationRuleExprs, rule::Rule,
14    },
15    pattern::{LocationPattern, LocationPatternKind, Pattern},
16};
17
18pub struct PatternMatcher<Kind, Item> {
19    map: BTreeMap<Pattern<Kind>, Item>,
20}
21
22pub type LocationPatternMatcher<Item> = PatternMatcher<LocationPatternKind, Item>;
23
24impl<Kind, Item> Default for PatternMatcher<Kind, Item> {
25    fn default() -> Self {
26        Self {
27            map: Default::default(),
28        }
29    }
30}
31
32impl<Kind, Item> FromIterator<(Pattern<Kind>, Item)> for PatternMatcher<Kind, Item>
33where
34    Pattern<Kind>: Ord,
35{
36    fn from_iter<T: IntoIterator<Item = (Pattern<Kind>, Item)>>(iter: T) -> Self {
37        Self {
38            map: iter.into_iter().collect(),
39        }
40    }
41}
42
43impl<Index> LocationPatternMatcher<Index> {
44    pub fn r#match<'set: 's, 's>(
45        &'set self,
46        s: &'s str,
47    ) -> Option<(&'set Index, &'set LocationPattern, &'s str)> {
48        // pattern has been shored by priority
49        self.map
50            .iter()
51            .find_map(|(pattern, index)| pattern.r#match(s).map(|s| (index, pattern, s)))
52    }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct PatternWithTime<Kind>
57where
58    Pattern<Kind>: FromStr<Err: Display>,
59{
60    timestamp: i64, // why i64?
61    pattern: Pattern<Kind>,
62}
63
64impl<Kind> Display for PatternWithTime<Kind>
65where
66    Pattern<Kind>: FromStr<Err: Display>,
67{
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        self.pattern.fmt(f)
70    }
71}
72
73impl<Kind> PatternWithTime<Kind>
74where
75    Pattern<Kind>: FromStr<Err: Display>,
76{
77    pub fn new(timestamp: i64, pattern: Pattern<Kind>) -> Self {
78        Self { timestamp, pattern }
79    }
80}
81
82impl<Kind> PartialOrd for PatternWithTime<Kind>
83where
84    Self: Ord,
85    Pattern<Kind>: FromStr<Err: Display>,
86{
87    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
88        Some(self.cmp(other))
89    }
90}
91
92impl Ord for PatternWithTime<LocationPatternKind> {
93    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
94        (self.pattern.priority().cmp(&other.pattern.priority()))
95            // 时间倒序,新规则更提前
96            .then_with(|| self.timestamp.cmp(&other.timestamp).reverse())
97    }
98}
99
100#[derive(snafu::Snafu, Debug)]
101pub enum MatchRuleFailed<MatchSet: snafu::Error + 'static> {
102    #[snafu(display("no rule set matched"))]
103    MatchSet { source: MatchSet },
104
105    #[snafu(display("rule set matched, but no rule matched"))]
106    MatchRuleInSet,
107}
108
109#[derive(Default, Debug, Clone, From, Into, PartialEq, Eq, Serialize, Deserialize)]
110pub struct LocationRulesMatcher {
111    pub map:
112        BTreeMap<PatternWithTime<LocationPatternKind>, Vec<(LocationRuleExprs, RequestAction)>>,
113}
114
115impl LocationRulesMatcher {
116    #[allow(clippy::type_complexity)]
117    pub fn match_rules(
118        &self,
119        path: &str,
120    ) -> Result<(&LocationPattern, &[(LocationRuleExprs, RequestAction)]), MatchLocationFailed>
121    {
122        use crate::error::location::NoMatchedPathSnafu;
123        self.map
124            .iter()
125            .find(|(PatternWithTime { pattern, .. }, ..)| pattern.is_match(path))
126            .map(|(PatternWithTime { pattern, .. }, rules)| (pattern, rules.as_slice()))
127            .context(NoMatchedPathSnafu {
128                path: path.to_string(),
129            })
130    }
131
132    pub fn match_rule<'r, NewRequest>(
133        &'r self,
134        path: &str,
135        new_request: &NewRequest,
136    ) -> Result<(&'r LocationPattern, RequestAction), MatchRuleFailed<MatchLocationFailed>>
137    where
138        Rule<'r, AtomicLocationRuleExpr, RequestAction>:
139            Evaluable<NewRequest, Value = Option<RequestAction>>,
140    {
141        let (location_pattern, rules) = self.match_rules(path).context(MatchSetSnafu)?;
142        rules
143            .iter()
144            // rules are ordered by created time. Reverse to make the latest rule evaluated first.
145            .rev()
146            .map(|(exprs, action)| Rule::new(exprs.polish(), *action))
147            .find_map(|rule| rule.eval(new_request))
148            .map(|action| (location_pattern, action))
149            .context(MatchRuleInSetSnafu)
150    }
151}