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 config: PathPredicateConfig,
29 regex: Regex,
31}
32
33impl PathPredicate {
34 pub fn new(config: PathPredicateConfig) -> Result<Self, ProxyError> {
36 let regex_pattern = Self::pattern_to_regex(&config.pattern);
38
39 let regex = Regex::new(®ex_pattern)
41 .map_err(|e| ProxyError::RoutingError(format!("Invalid path predicate regex pattern '{}': {}", config.pattern, e)))?;
42
43 Ok(Self { config, regex })
44 }
45
46 fn pattern_to_regex(pattern: &str) -> String {
48 let mut regex_pattern = "^".to_string();
49
50 let mut chars = pattern.chars().peekable();
51 while let Some(c) = chars.next() {
52 match c {
53 ':' => {
55 let mut param_name = String::new();
56 while let Some(&next_char) = chars.peek() {
57 if next_char.is_alphanumeric() || next_char == '_' {
58 param_name.push(chars.next().unwrap());
59 } else {
60 break;
61 }
62 }
63
64 regex_pattern.push_str(&format!("([^/]+)"));
66 },
67 '*' => {
69 regex_pattern.push_str("(.*)");
70 },
71 '.' | '^' | '$' | '|' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '\\' => {
73 regex_pattern.push('\\');
74 regex_pattern.push(c);
75 },
76 _ => {
78 regex_pattern.push(c);
79 }
80 }
81 }
82
83 regex_pattern.push('$');
84 regex_pattern
85 }
86}
87
88#[async_trait]
89impl Predicate for PathPredicate {
90 async fn matches(&self, request: &ProxyRequest) -> bool {
91 self.regex.is_match(&request.path)
92 }
93
94 fn predicate_type(&self) -> &str {
95 "path"
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct MethodPredicateConfig {
102 pub methods: Vec<HttpMethod>,
104}
105
106#[derive(Debug)]
108pub struct MethodPredicate {
109 config: MethodPredicateConfig,
111}
112
113impl MethodPredicate {
114 pub fn new(config: MethodPredicateConfig) -> Self {
116 Self { config }
117 }
118}
119
120#[async_trait]
121impl Predicate for MethodPredicate {
122 async fn matches(&self, request: &ProxyRequest) -> bool {
123 self.config.methods.contains(&request.method)
124 }
125
126 fn predicate_type(&self) -> &str {
127 "method"
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct HeaderPredicateConfig {
134 pub headers: HashMap<String, String>,
136 #[serde(default)]
138 pub exact_match: bool,
139}
140
141#[derive(Debug)]
143pub struct HeaderPredicate {
144 config: HeaderPredicateConfig,
146}
147
148impl HeaderPredicate {
149 pub fn new(config: HeaderPredicateConfig) -> Self {
151 Self { config }
152 }
153}
154
155#[async_trait]
156impl Predicate for HeaderPredicate {
157 async fn matches(&self, request: &ProxyRequest) -> bool {
158 for (name, expected_value) in &self.config.headers {
159 if let Some(header_value) = request.headers.get(name) {
161 if let Ok(actual_value) = header_value.to_str() {
163 if self.config.exact_match {
164 if actual_value != expected_value {
166 return false;
167 }
168 } else {
169 if !actual_value.contains(expected_value) {
171 return false;
172 }
173 }
174 } else {
175 return false;
177 }
178 } else {
179 return false;
181 }
182 }
183
184 true
186 }
187
188 fn predicate_type(&self) -> &str {
189 "header"
190 }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct QueryPredicateConfig {
196 pub params: HashMap<String, String>,
198 #[serde(default)]
200 pub exact_match: bool,
201}
202
203#[derive(Debug)]
205pub struct QueryPredicate {
206 config: QueryPredicateConfig,
208}
209
210impl QueryPredicate {
211 pub fn new(config: QueryPredicateConfig) -> Self {
213 Self { config }
214 }
215
216 fn parse_query_params(query: &str) -> HashMap<String, String> {
218 let mut params = HashMap::new();
219
220 for pair in query.split('&') {
221 let mut iter = pair.split('=');
222 if let (Some(key), Some(value)) = (iter.next(), iter.next()) {
223 params.insert(key.to_string(), value.to_string());
224 }
225 }
226
227 params
228 }
229}
230
231#[async_trait]
232impl Predicate for QueryPredicate {
233 async fn matches(&self, request: &ProxyRequest) -> bool {
234 if self.config.params.is_empty() {
236 return true;
237 }
238
239 if let Some(query) = &request.query {
241 let params = Self::parse_query_params(query);
242
243 for (name, expected_value) in &self.config.params {
244 if let Some(actual_value) = params.get(name) {
246 if self.config.exact_match {
247 if actual_value != expected_value {
249 return false;
250 }
251 } else {
252 if !actual_value.contains(expected_value) {
254 return false;
255 }
256 }
257 } else {
258 return false;
260 }
261 }
262
263 true
265 } else {
266 false
267 }
268 }
269
270 fn predicate_type(&self) -> &str {
271 "query"
272 }
273}