foxy/router/
predicates.rs1use async_trait::async_trait;
10use regex::Regex;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14use super::Predicate;
15use crate::core::{HttpMethod, ProxyError, ProxyRequest};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct PathPredicateConfig {
20 pub pattern: String,
22}
23
24#[derive(Debug)]
26pub struct PathPredicate {
27 #[allow(dead_code)]
29 config: PathPredicateConfig,
30 regex: Regex,
32}
33
34impl PathPredicate {
35 pub fn new(config: PathPredicateConfig) -> Result<Self, ProxyError> {
37 let regex_pattern = Self::pattern_to_regex(&config.pattern);
39
40 let regex = Regex::new(®ex_pattern).map_err(|e| {
42 ProxyError::RoutingError(format!(
43 "Invalid path predicate regex pattern '{}': {}",
44 config.pattern, e
45 ))
46 })?;
47
48 Ok(Self { config, regex })
49 }
50
51 fn pattern_to_regex(pattern: &str) -> String {
53 let mut regex_pattern = "^".to_string();
54
55 let mut chars = pattern.chars().peekable();
56 while let Some(c) = chars.next() {
57 match c {
58 ':' => {
60 let mut param_name = String::new();
61 while let Some(&next_char) = chars.peek() {
62 if next_char.is_alphanumeric() || next_char == '_' {
63 param_name.push(chars.next().unwrap());
64 } else {
65 break;
66 }
67 }
68
69 regex_pattern.push_str("([^/]+)");
71 }
72 '*' => {
74 regex_pattern.push_str("(.*)");
75 }
76 '.' | '^' | '$' | '|' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '\\' => {
78 regex_pattern.push('\\');
79 regex_pattern.push(c);
80 }
81 _ => {
83 regex_pattern.push(c);
84 }
85 }
86 }
87
88 regex_pattern.push('$');
89 regex_pattern
90 }
91}
92
93#[async_trait]
94impl Predicate for PathPredicate {
95 async fn matches(&self, request: &ProxyRequest) -> bool {
96 self.regex.is_match(&request.path)
97 }
98
99 fn predicate_type(&self) -> &str {
100 "path"
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct MethodPredicateConfig {
107 pub methods: Vec<HttpMethod>,
109}
110
111#[derive(Debug)]
113pub struct MethodPredicate {
114 config: MethodPredicateConfig,
116}
117
118impl MethodPredicate {
119 pub fn new(config: MethodPredicateConfig) -> Self {
121 Self { config }
122 }
123}
124
125#[async_trait]
126impl Predicate for MethodPredicate {
127 async fn matches(&self, request: &ProxyRequest) -> bool {
128 self.config.methods.contains(&request.method)
129 }
130
131 fn predicate_type(&self) -> &str {
132 "method"
133 }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct HeaderPredicateConfig {
139 pub headers: HashMap<String, String>,
141 #[serde(default)]
143 pub exact_match: bool,
144}
145
146#[derive(Debug)]
148pub struct HeaderPredicate {
149 config: HeaderPredicateConfig,
151}
152
153impl HeaderPredicate {
154 pub fn new(config: HeaderPredicateConfig) -> Self {
156 Self { config }
157 }
158}
159
160#[async_trait]
161impl Predicate for HeaderPredicate {
162 async fn matches(&self, request: &ProxyRequest) -> bool {
163 for (name, expected_value) in &self.config.headers {
164 if let Some(header_value) = request.headers.get(name) {
166 if let Ok(actual_value) = header_value.to_str() {
168 if self.config.exact_match {
169 if actual_value != expected_value {
171 return false;
172 }
173 } else {
174 if !actual_value.contains(expected_value) {
176 return false;
177 }
178 }
179 } else {
180 return false;
182 }
183 } else {
184 return false;
186 }
187 }
188
189 true
191 }
192
193 fn predicate_type(&self) -> &str {
194 "header"
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct QueryPredicateConfig {
201 pub params: HashMap<String, String>,
203 #[serde(default)]
205 pub exact_match: bool,
206}
207
208#[derive(Debug)]
210pub struct QueryPredicate {
211 config: QueryPredicateConfig,
213}
214
215impl QueryPredicate {
216 pub fn new(config: QueryPredicateConfig) -> Self {
218 Self { config }
219 }
220
221 fn parse_query_params(query: &str) -> HashMap<String, String> {
223 let mut params = HashMap::new();
224
225 for pair in query.split('&') {
226 let mut iter = pair.split('=');
227 if let (Some(key), Some(value)) = (iter.next(), iter.next()) {
228 params.insert(key.to_string(), value.to_string());
229 }
230 }
231
232 params
233 }
234}
235
236#[async_trait]
237impl Predicate for QueryPredicate {
238 async fn matches(&self, request: &ProxyRequest) -> bool {
239 if self.config.params.is_empty() {
241 return true;
242 }
243
244 if let Some(query) = &request.query {
246 let params = Self::parse_query_params(query);
247
248 for (name, expected_value) in &self.config.params {
249 if let Some(actual_value) = params.get(name) {
251 if self.config.exact_match {
252 if actual_value != expected_value {
254 return false;
255 }
256 } else {
257 if !actual_value.contains(expected_value) {
259 return false;
260 }
261 }
262 } else {
263 return false;
265 }
266 }
267
268 true
270 } else {
271 false
272 }
273 }
274
275 fn predicate_type(&self) -> &str {
276 "query"
277 }
278}