foxy/router/
predicates.rs1use std::collections::HashMap;
10use async_trait::async_trait;
11use regex::Regex;
12use serde::{Serialize, Deserialize};
13
14use crate::core::{ProxyRequest, HttpMethod, ProxyError};
15use super::Predicate;
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)
42 .map_err(|e| ProxyError::RoutingError(format!("Invalid path predicate regex pattern '{}': {}", config.pattern, e)))?;
43
44 Ok(Self { config, regex })
45 }
46
47 fn pattern_to_regex(pattern: &str) -> String {
49 let mut regex_pattern = "^".to_string();
50
51 let mut chars = pattern.chars().peekable();
52 while let Some(c) = chars.next() {
53 match c {
54 ':' => {
56 let mut param_name = String::new();
57 while let Some(&next_char) = chars.peek() {
58 if next_char.is_alphanumeric() || next_char == '_' {
59 param_name.push(chars.next().unwrap());
60 } else {
61 break;
62 }
63 }
64
65 regex_pattern.push_str("([^/]+)");
67 },
68 '*' => {
70 regex_pattern.push_str("(.*)");
71 },
72 '.' | '^' | '$' | '|' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '\\' => {
74 regex_pattern.push('\\');
75 regex_pattern.push(c);
76 },
77 _ => {
79 regex_pattern.push(c);
80 }
81 }
82 }
83
84 regex_pattern.push('$');
85 regex_pattern
86 }
87}
88
89#[async_trait]
90impl Predicate for PathPredicate {
91 async fn matches(&self, request: &ProxyRequest) -> bool {
92 self.regex.is_match(&request.path)
93 }
94
95 fn predicate_type(&self) -> &str {
96 "path"
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct MethodPredicateConfig {
103 pub methods: Vec<HttpMethod>,
105}
106
107#[derive(Debug)]
109pub struct MethodPredicate {
110 config: MethodPredicateConfig,
112}
113
114impl MethodPredicate {
115 pub fn new(config: MethodPredicateConfig) -> Self {
117 Self { config }
118 }
119}
120
121#[async_trait]
122impl Predicate for MethodPredicate {
123 async fn matches(&self, request: &ProxyRequest) -> bool {
124 self.config.methods.contains(&request.method)
125 }
126
127 fn predicate_type(&self) -> &str {
128 "method"
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct HeaderPredicateConfig {
135 pub headers: HashMap<String, String>,
137 #[serde(default)]
139 pub exact_match: bool,
140}
141
142#[derive(Debug)]
144pub struct HeaderPredicate {
145 config: HeaderPredicateConfig,
147}
148
149impl HeaderPredicate {
150 pub fn new(config: HeaderPredicateConfig) -> Self {
152 Self { config }
153 }
154}
155
156#[async_trait]
157impl Predicate for HeaderPredicate {
158 async fn matches(&self, request: &ProxyRequest) -> bool {
159 for (name, expected_value) in &self.config.headers {
160 if let Some(header_value) = request.headers.get(name) {
162 if let Ok(actual_value) = header_value.to_str() {
164 if self.config.exact_match {
165 if actual_value != expected_value {
167 return false;
168 }
169 } else {
170 if !actual_value.contains(expected_value) {
172 return false;
173 }
174 }
175 } else {
176 return false;
178 }
179 } else {
180 return false;
182 }
183 }
184
185 true
187 }
188
189 fn predicate_type(&self) -> &str {
190 "header"
191 }
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct QueryPredicateConfig {
197 pub params: HashMap<String, String>,
199 #[serde(default)]
201 pub exact_match: bool,
202}
203
204#[derive(Debug)]
206pub struct QueryPredicate {
207 config: QueryPredicateConfig,
209}
210
211impl QueryPredicate {
212 pub fn new(config: QueryPredicateConfig) -> Self {
214 Self { config }
215 }
216
217 fn parse_query_params(query: &str) -> HashMap<String, String> {
219 let mut params = HashMap::new();
220
221 for pair in query.split('&') {
222 let mut iter = pair.split('=');
223 if let (Some(key), Some(value)) = (iter.next(), iter.next()) {
224 params.insert(key.to_string(), value.to_string());
225 }
226 }
227
228 params
229 }
230}
231
232#[async_trait]
233impl Predicate for QueryPredicate {
234 async fn matches(&self, request: &ProxyRequest) -> bool {
235 if self.config.params.is_empty() {
237 return true;
238 }
239
240 if let Some(query) = &request.query {
242 let params = Self::parse_query_params(query);
243
244 for (name, expected_value) in &self.config.params {
245 if let Some(actual_value) = params.get(name) {
247 if self.config.exact_match {
248 if actual_value != expected_value {
250 return false;
251 }
252 } else {
253 if !actual_value.contains(expected_value) {
255 return false;
256 }
257 }
258 } else {
259 return false;
261 }
262 }
263
264 true
266 } else {
267 false
268 }
269 }
270
271 fn predicate_type(&self) -> &str {
272 "query"
273 }
274}