mockforge_core/proxy/
conditional.rs1use crate::conditions::{evaluate_condition, ConditionContext};
4use crate::proxy::config::ProxyRule;
5use crate::{Error, Result};
6use axum::http::{HeaderMap, Method, Uri};
7use serde_json::Value;
8use std::collections::HashMap;
9use tracing::debug;
10
11pub fn evaluate_proxy_condition(
13 rule: &ProxyRule,
14 method: &Method,
15 uri: &Uri,
16 headers: &HeaderMap,
17 body: Option<&[u8]>,
18) -> Result<bool> {
19 let Some(ref condition) = rule.condition else {
21 return Ok(true);
22 };
23
24 let mut context = ConditionContext::new()
26 .with_method(method.as_str().to_string())
27 .with_path(uri.path().to_string());
28
29 let query_params: HashMap<String, String> = uri
31 .query()
32 .map(|q| {
33 url::form_urlencoded::parse(q.as_bytes())
34 .map(|(k, v)| (k.to_string(), v.to_string()))
35 .collect()
36 })
37 .unwrap_or_default();
38 context = context.with_query_params(query_params);
39
40 let headers_map: HashMap<String, String> = headers
42 .iter()
43 .filter_map(|(k, v)| {
44 v.to_str().ok().map(|v_str| (k.as_str().to_lowercase(), v_str.to_string()))
45 })
46 .collect();
47 context = context.with_headers(headers_map);
48
49 if let Some(body_bytes) = body {
51 if let Ok(body_str) = std::str::from_utf8(body_bytes) {
52 if let Ok(json_value) = serde_json::from_str::<Value>(body_str) {
54 context = context.with_request_body(json_value);
55 }
56 }
57 }
58
59 match evaluate_condition(condition, &context) {
61 Ok(result) => {
62 debug!(
63 "Proxy condition '{}' evaluated to {} for {} {}",
64 condition,
65 result,
66 method,
67 uri.path()
68 );
69 Ok(result)
70 }
71 Err(e) => {
72 tracing::warn!(
74 "Failed to evaluate proxy condition '{}': {}. Treating as false.",
75 condition,
76 e
77 );
78 Ok(false)
79 }
80 }
81}
82
83pub fn find_matching_rule<'a>(
85 rules: &'a [ProxyRule],
86 method: &Method,
87 uri: &Uri,
88 headers: &HeaderMap,
89 body: Option<&[u8]>,
90 path_matches: impl Fn(&str, &str) -> bool,
91) -> Option<&'a ProxyRule> {
92 for rule in rules {
93 if !rule.enabled {
94 continue;
95 }
96
97 if !path_matches(&rule.path_pattern, uri.path()) {
99 continue;
100 }
101
102 match evaluate_proxy_condition(rule, method, uri, headers, body) {
104 Ok(true) => return Some(rule),
105 Ok(false) => continue, Err(e) => {
107 tracing::warn!("Error evaluating condition for rule {}: {}", rule.path_pattern, e);
108 continue;
109 }
110 }
111 }
112
113 None
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::proxy::config::ProxyRule;
120 use axum::http::HeaderValue;
121 use serde_json::json;
122
123 fn create_test_rule(path: &str, condition: Option<&str>) -> ProxyRule {
124 ProxyRule {
125 path_pattern: path.to_string(),
126 target_url: "http://example.com".to_string(),
127 enabled: true,
128 pattern: path.to_string(),
129 upstream_url: "http://example.com".to_string(),
130 migration_mode: crate::proxy::config::MigrationMode::Auto,
131 migration_group: None,
132 condition: condition.map(|s| s.to_string()),
133 }
134 }
135
136 #[test]
137 fn test_no_condition() {
138 let rule = create_test_rule("/api/users", None);
139 let method = Method::GET;
140 let uri = Uri::from_static("/api/users");
141 let headers = HeaderMap::new();
142
143 let result = evaluate_proxy_condition(&rule, &method, &uri, &headers, None).unwrap();
144 assert!(result); }
146
147 #[test]
148 fn test_header_condition() {
149 let rule = create_test_rule("/api/users", Some("header[authorization] != ''"));
150 let method = Method::GET;
151 let uri = Uri::from_static("/api/users");
152 let mut headers = HeaderMap::new();
153 headers.insert("authorization", HeaderValue::from_static("Bearer token123"));
154
155 let result = evaluate_proxy_condition(&rule, &method, &uri, &headers, None).unwrap();
156 assert!(result);
157 }
158
159 #[test]
160 fn test_jsonpath_condition() {
161 let rule = create_test_rule("/api/users", Some("$.user.role"));
162 let method = Method::POST;
163 let uri = Uri::from_static("/api/users");
164 let headers = HeaderMap::new();
165 let body = json!({
166 "user": {
167 "role": "admin"
168 }
169 });
170 let body_bytes = serde_json::to_string(&body).unwrap().into_bytes();
171
172 let result =
173 evaluate_proxy_condition(&rule, &method, &uri, &headers, Some(&body_bytes)).unwrap();
174 assert!(result);
175 }
176
177 #[test]
178 fn test_query_param_condition() {
179 let rule = create_test_rule("/api/users", Some("query[env] == 'production'"));
180 let method = Method::GET;
181 let uri = Uri::from_static("/api/users?env=production");
182 let headers = HeaderMap::new();
183
184 let result = evaluate_proxy_condition(&rule, &method, &uri, &headers, None).unwrap();
185 assert!(result);
186 }
187
188 #[test]
189 fn test_complex_condition() {
190 let rule = create_test_rule(
191 "/api/users",
192 Some("AND(header[authorization] != '', query[env] == 'production')"),
193 );
194 let method = Method::GET;
195 let uri = Uri::from_static("/api/users?env=production");
196 let mut headers = HeaderMap::new();
197 headers.insert("authorization", HeaderValue::from_static("Bearer token"));
198
199 let result = evaluate_proxy_condition(&rule, &method, &uri, &headers, None).unwrap();
200 assert!(result);
201 }
202}