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