mockforge_grpc/reflection/
config.rs1use crate::reflection::error_handling::ErrorConfig;
4use mockforge_core::overrides::Overrides;
5use mockforge_openapi::openapi_routes::ValidationMode;
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8
9#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct ProxyConfig {
12 pub allowlist: HashSet<String>,
14 pub denylist: HashSet<String>,
16 pub require_explicit_allow: bool,
18 pub grpc_port: u16,
20 pub error_config: Option<ErrorConfig>,
22 pub response_transform: ResponseTransformConfig,
24 pub upstream_endpoint: Option<String>,
26 pub mock_seed: Option<u64>,
28 pub request_timeout_seconds: u64,
30 pub admin_skip_prefixes: Vec<String>,
32 pub overrides: HashMap<String, ValidationMode>,
34 pub request_mode: ValidationMode,
36}
37
38impl Default for ProxyConfig {
39 fn default() -> Self {
40 Self {
41 allowlist: HashSet::new(),
42 denylist: HashSet::new(),
43 require_explicit_allow: false,
44 grpc_port: default_grpc_port(),
45 error_config: None,
46 response_transform: ResponseTransformConfig::default(),
47 upstream_endpoint: None,
48 mock_seed: None,
49 request_timeout_seconds: default_request_timeout_seconds(),
50 admin_skip_prefixes: Vec::new(),
51 overrides: HashMap::new(),
52 request_mode: ValidationMode::default(),
53 }
54 }
55}
56
57fn default_grpc_port() -> u16 {
59 50051
60}
61
62fn default_request_timeout_seconds() -> u64 {
64 30
65}
66
67#[derive(Debug, Clone, Deserialize, Serialize, Default)]
69pub struct ResponseTransformConfig {
70 pub enabled: bool,
72 pub custom_headers: HashMap<String, String>,
74 pub overrides: Option<Overrides>,
76 pub validate_responses: bool,
78}
79
80impl ProxyConfig {
81 pub fn is_service_allowed(&self, service_name: &str) -> bool {
83 if self.denylist.contains(service_name) {
85 return false;
86 }
87
88 if self.require_explicit_allow
90 && !self.allowlist.is_empty()
91 && !self.allowlist.contains(service_name)
92 {
93 return false;
94 }
95
96 true
97 }
98
99 pub fn is_service_denied(&self, service_name: &str) -> bool {
101 self.denylist.contains(service_name)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
112 fn test_proxy_config_default() {
113 let config = ProxyConfig::default();
114
115 assert!(config.allowlist.is_empty());
116 assert!(config.denylist.is_empty());
117 assert!(!config.require_explicit_allow);
118 assert_eq!(config.grpc_port, 50051);
119 assert!(config.error_config.is_none());
120 assert!(config.upstream_endpoint.is_none());
121 assert!(config.mock_seed.is_none());
122 assert_eq!(config.request_timeout_seconds, 30);
123 assert!(config.admin_skip_prefixes.is_empty());
124 assert!(config.overrides.is_empty());
125 }
126
127 #[test]
128 fn test_proxy_config_default_grpc_port() {
129 assert_eq!(default_grpc_port(), 50051);
130 }
131
132 #[test]
133 fn test_proxy_config_default_timeout() {
134 assert_eq!(default_request_timeout_seconds(), 30);
135 }
136
137 #[test]
140 fn test_is_service_allowed_empty_lists() {
141 let config = ProxyConfig::default();
142
143 assert!(config.is_service_allowed("any.service.Name"));
144 assert!(config.is_service_allowed("another.Service"));
145 }
146
147 #[test]
148 fn test_is_service_allowed_in_allowlist() {
149 let mut config = ProxyConfig::default();
150 config.allowlist.insert("my.allowed.Service".to_string());
151 config.require_explicit_allow = true;
152
153 assert!(config.is_service_allowed("my.allowed.Service"));
154 }
155
156 #[test]
157 fn test_is_service_allowed_not_in_allowlist_explicit() {
158 let mut config = ProxyConfig::default();
159 config.allowlist.insert("my.allowed.Service".to_string());
160 config.require_explicit_allow = true;
161
162 assert!(!config.is_service_allowed("other.Service"));
163 }
164
165 #[test]
166 fn test_is_service_allowed_not_explicit_mode() {
167 let mut config = ProxyConfig::default();
168 config.allowlist.insert("my.allowed.Service".to_string());
169 config.require_explicit_allow = false;
170
171 assert!(config.is_service_allowed("other.Service"));
173 assert!(config.is_service_allowed("my.allowed.Service"));
174 }
175
176 #[test]
179 fn test_is_service_denied_empty_denylist() {
180 let config = ProxyConfig::default();
181
182 assert!(!config.is_service_denied("any.service.Name"));
183 }
184
185 #[test]
186 fn test_is_service_denied_in_denylist() {
187 let mut config = ProxyConfig::default();
188 config.denylist.insert("blocked.Service".to_string());
189
190 assert!(config.is_service_denied("blocked.Service"));
191 assert!(!config.is_service_denied("other.Service"));
192 }
193
194 #[test]
195 fn test_denylist_takes_precedence() {
196 let mut config = ProxyConfig::default();
197 config.allowlist.insert("my.Service".to_string());
198 config.denylist.insert("my.Service".to_string());
199 config.require_explicit_allow = true;
200
201 assert!(!config.is_service_allowed("my.Service"));
203 }
204
205 #[test]
206 fn test_multiple_services_in_denylist() {
207 let mut config = ProxyConfig::default();
208 config.denylist.insert("blocked1.Service".to_string());
209 config.denylist.insert("blocked2.Service".to_string());
210 config.denylist.insert("blocked3.Service".to_string());
211
212 assert!(config.is_service_denied("blocked1.Service"));
213 assert!(config.is_service_denied("blocked2.Service"));
214 assert!(config.is_service_denied("blocked3.Service"));
215 assert!(!config.is_service_denied("allowed.Service"));
216 }
217
218 #[test]
221 fn test_response_transform_config_default() {
222 let config = ResponseTransformConfig::default();
223
224 assert!(!config.enabled);
225 assert!(config.custom_headers.is_empty());
226 assert!(config.overrides.is_none());
227 assert!(!config.validate_responses);
228 }
229
230 #[test]
231 fn test_response_transform_config_with_headers() {
232 let config = ResponseTransformConfig {
233 enabled: true,
234 custom_headers: {
235 let mut headers = HashMap::new();
236 headers.insert("X-Custom-Header".to_string(), "value".to_string());
237 headers
238 },
239 ..Default::default()
240 };
241
242 assert!(config.enabled);
243 assert_eq!(config.custom_headers.get("X-Custom-Header"), Some(&"value".to_string()));
244 }
245
246 #[test]
247 fn test_response_transform_config_with_validation() {
248 let config = ResponseTransformConfig {
249 validate_responses: true,
250 ..Default::default()
251 };
252
253 assert!(config.validate_responses);
254 }
255
256 #[test]
259 fn test_proxy_config_serialization() {
260 let config = ProxyConfig::default();
261
262 let json = serde_json::to_string(&config).unwrap();
263 let deserialized: ProxyConfig = serde_json::from_str(&json).unwrap();
264
265 assert_eq!(deserialized.grpc_port, config.grpc_port);
266 assert_eq!(deserialized.require_explicit_allow, config.require_explicit_allow);
267 assert_eq!(deserialized.request_timeout_seconds, config.request_timeout_seconds);
268 }
269
270 #[test]
271 fn test_proxy_config_deserialization() {
272 let json = r#"{
273 "allowlist": ["service1", "service2"],
274 "denylist": ["blocked"],
275 "require_explicit_allow": true,
276 "grpc_port": 9090,
277 "error_config": null,
278 "response_transform": {
279 "enabled": false,
280 "custom_headers": {},
281 "overrides": null,
282 "validate_responses": false
283 },
284 "upstream_endpoint": "http://localhost:50051",
285 "mock_seed": 12345,
286 "request_timeout_seconds": 60,
287 "admin_skip_prefixes": ["/admin"],
288 "overrides": {},
289 "request_mode": "Enforce"
290 }"#;
291
292 let config: ProxyConfig = serde_json::from_str(json).unwrap();
293
294 assert_eq!(config.allowlist.len(), 2);
295 assert!(config.allowlist.contains("service1"));
296 assert!(config.allowlist.contains("service2"));
297 assert!(config.denylist.contains("blocked"));
298 assert!(config.require_explicit_allow);
299 assert_eq!(config.grpc_port, 9090);
300 assert_eq!(config.upstream_endpoint, Some("http://localhost:50051".to_string()));
301 assert_eq!(config.mock_seed, Some(12345));
302 assert_eq!(config.request_timeout_seconds, 60);
303 }
304
305 #[test]
306 fn test_response_transform_config_serialization() {
307 let config = ResponseTransformConfig {
308 enabled: true,
309 custom_headers: {
310 let mut headers = HashMap::new();
311 headers.insert("X-Test".to_string(), "test".to_string());
312 headers
313 },
314 ..Default::default()
315 };
316
317 let json = serde_json::to_string(&config).unwrap();
318 let deserialized: ResponseTransformConfig = serde_json::from_str(&json).unwrap();
319
320 assert_eq!(deserialized.enabled, config.enabled);
321 assert_eq!(deserialized.custom_headers, config.custom_headers);
322 }
323
324 #[test]
327 fn test_proxy_config_clone() {
328 let mut config = ProxyConfig::default();
329 config.allowlist.insert("service1".to_string());
330 config.grpc_port = 8080;
331
332 let cloned = config.clone();
333
334 assert_eq!(cloned.allowlist, config.allowlist);
335 assert_eq!(cloned.grpc_port, config.grpc_port);
336 }
337
338 #[test]
339 fn test_response_transform_config_clone() {
340 let config = ResponseTransformConfig {
341 enabled: true,
342 ..Default::default()
343 };
344
345 let cloned = config.clone();
346
347 assert_eq!(cloned.enabled, config.enabled);
348 }
349
350 #[test]
353 fn test_empty_allowlist_with_explicit_allow() {
354 let config = ProxyConfig {
355 require_explicit_allow: true,
356 ..Default::default()
357 };
358 assert!(config.is_service_allowed("any.Service"));
363 }
364
365 #[test]
366 fn test_special_characters_in_service_name() {
367 let mut config = ProxyConfig::default();
368 config.allowlist.insert("com.example.v1.MyService".to_string());
369 config.require_explicit_allow = true;
370
371 assert!(config.is_service_allowed("com.example.v1.MyService"));
372 assert!(!config.is_service_allowed("com.example.v2.MyService"));
373 }
374
375 #[test]
376 fn test_case_sensitive_service_names() {
377 let mut config = ProxyConfig::default();
378 config.allowlist.insert("MyService".to_string());
379 config.require_explicit_allow = true;
380
381 assert!(config.is_service_allowed("MyService"));
382 assert!(!config.is_service_allowed("myservice"));
383 assert!(!config.is_service_allowed("MYSERVICE"));
384 }
385}