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 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, 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 .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 .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}