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: std::collections::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 mut config = ResponseTransformConfig::default();
232 config.enabled = true;
233 config.custom_headers.insert("X-Custom-Header".to_string(), "value".to_string());
234
235 assert!(config.enabled);
236 assert_eq!(config.custom_headers.get("X-Custom-Header"), Some(&"value".to_string()));
237 }
238
239 #[test]
240 fn test_response_transform_config_with_validation() {
241 let mut config = ResponseTransformConfig::default();
242 config.validate_responses = true;
243
244 assert!(config.validate_responses);
245 }
246
247 #[test]
250 fn test_proxy_config_serialization() {
251 let config = ProxyConfig::default();
252
253 let json = serde_json::to_string(&config).unwrap();
254 let deserialized: ProxyConfig = serde_json::from_str(&json).unwrap();
255
256 assert_eq!(deserialized.grpc_port, config.grpc_port);
257 assert_eq!(deserialized.require_explicit_allow, config.require_explicit_allow);
258 assert_eq!(deserialized.request_timeout_seconds, config.request_timeout_seconds);
259 }
260
261 #[test]
262 fn test_proxy_config_deserialization() {
263 let json = r#"{
264 "allowlist": ["service1", "service2"],
265 "denylist": ["blocked"],
266 "require_explicit_allow": true,
267 "grpc_port": 9090,
268 "error_config": null,
269 "response_transform": {
270 "enabled": false,
271 "custom_headers": {},
272 "overrides": null,
273 "validate_responses": false
274 },
275 "upstream_endpoint": "http://localhost:50051",
276 "mock_seed": 12345,
277 "request_timeout_seconds": 60,
278 "admin_skip_prefixes": ["/admin"],
279 "overrides": {},
280 "request_mode": "Enforce"
281 }"#;
282
283 let config: ProxyConfig = serde_json::from_str(json).unwrap();
284
285 assert_eq!(config.allowlist.len(), 2);
286 assert!(config.allowlist.contains("service1"));
287 assert!(config.allowlist.contains("service2"));
288 assert!(config.denylist.contains("blocked"));
289 assert!(config.require_explicit_allow);
290 assert_eq!(config.grpc_port, 9090);
291 assert_eq!(config.upstream_endpoint, Some("http://localhost:50051".to_string()));
292 assert_eq!(config.mock_seed, Some(12345));
293 assert_eq!(config.request_timeout_seconds, 60);
294 }
295
296 #[test]
297 fn test_response_transform_config_serialization() {
298 let mut config = ResponseTransformConfig::default();
299 config.enabled = true;
300 config.custom_headers.insert("X-Test".to_string(), "test".to_string());
301
302 let json = serde_json::to_string(&config).unwrap();
303 let deserialized: ResponseTransformConfig = serde_json::from_str(&json).unwrap();
304
305 assert_eq!(deserialized.enabled, config.enabled);
306 assert_eq!(deserialized.custom_headers, config.custom_headers);
307 }
308
309 #[test]
312 fn test_proxy_config_clone() {
313 let mut config = ProxyConfig::default();
314 config.allowlist.insert("service1".to_string());
315 config.grpc_port = 8080;
316
317 let cloned = config.clone();
318
319 assert_eq!(cloned.allowlist, config.allowlist);
320 assert_eq!(cloned.grpc_port, config.grpc_port);
321 }
322
323 #[test]
324 fn test_response_transform_config_clone() {
325 let mut config = ResponseTransformConfig::default();
326 config.enabled = true;
327
328 let cloned = config.clone();
329
330 assert_eq!(cloned.enabled, config.enabled);
331 }
332
333 #[test]
336 fn test_empty_allowlist_with_explicit_allow() {
337 let mut config = ProxyConfig::default();
338 config.require_explicit_allow = true;
339 assert!(config.is_service_allowed("any.Service"));
344 }
345
346 #[test]
347 fn test_special_characters_in_service_name() {
348 let mut config = ProxyConfig::default();
349 config.allowlist.insert("com.example.v1.MyService".to_string());
350 config.require_explicit_allow = true;
351
352 assert!(config.is_service_allowed("com.example.v1.MyService"));
353 assert!(!config.is_service_allowed("com.example.v2.MyService"));
354 }
355
356 #[test]
357 fn test_case_sensitive_service_names() {
358 let mut config = ProxyConfig::default();
359 config.allowlist.insert("MyService".to_string());
360 config.require_explicit_allow = true;
361
362 assert!(config.is_service_allowed("MyService"));
363 assert!(!config.is_service_allowed("myservice"));
364 assert!(!config.is_service_allowed("MYSERVICE"));
365 }
366}