mockforge_core/proxy/
config.rs

1//! Proxy configuration types and settings
2
3use serde::{Deserialize, Serialize};
4
5/// Configuration for proxy behavior
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ProxyConfig {
8    /// Whether the proxy is enabled
9    pub enabled: bool,
10    /// Target URL to proxy requests to
11    pub target_url: Option<String>,
12    /// Timeout for proxy requests in seconds
13    pub timeout_seconds: u64,
14    /// Whether to follow redirects
15    pub follow_redirects: bool,
16    /// Additional headers to add to proxied requests
17    pub headers: std::collections::HashMap<String, String>,
18    /// Proxy prefix to strip from paths
19    pub prefix: Option<String>,
20    /// Whether to proxy by default
21    pub passthrough_by_default: bool,
22    /// Proxy rules
23    pub rules: Vec<ProxyRule>,
24}
25
26/// Proxy routing rule
27#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
28pub struct ProxyRule {
29    /// Path pattern to match
30    pub path_pattern: String,
31    /// Target URL for this rule
32    pub target_url: String,
33    /// Whether this rule is enabled
34    pub enabled: bool,
35    /// Pattern for matching (alias for path_pattern)
36    pub pattern: String,
37    /// Upstream URL (alias for target_url)
38    pub upstream_url: String,
39}
40
41impl Default for ProxyRule {
42    fn default() -> Self {
43        Self {
44            path_pattern: "/".to_string(),
45            target_url: "http://localhost:9080".to_string(),
46            enabled: true,
47            pattern: "/".to_string(),
48            upstream_url: "http://localhost:9080".to_string(),
49        }
50    }
51}
52
53impl ProxyConfig {
54    /// Create a new proxy configuration
55    pub fn new(upstream_url: String) -> Self {
56        Self {
57            enabled: true,
58            target_url: Some(upstream_url),
59            timeout_seconds: 30,
60            follow_redirects: true,
61            headers: std::collections::HashMap::new(),
62            prefix: Some("/proxy/".to_string()),
63            passthrough_by_default: true,
64            rules: Vec::new(),
65        }
66    }
67
68    /// Check if a request should be proxied
69    pub fn should_proxy(&self, _method: &axum::http::Method, path: &str) -> bool {
70        if !self.enabled {
71            return false;
72        }
73
74        // If there are rules, check if any rule matches
75        for rule in &self.rules {
76            if rule.enabled && self.path_matches_pattern(&rule.path_pattern, path) {
77                return true;
78            }
79        }
80
81        // If no rules match, check prefix logic
82        match &self.prefix {
83            None => true, // No prefix means proxy everything
84            Some(prefix) => path.starts_with(prefix),
85        }
86    }
87
88    /// Get the upstream URL for a specific path
89    pub fn get_upstream_url(&self, path: &str) -> String {
90        // Check rules first
91        for rule in &self.rules {
92            if rule.enabled && self.path_matches_pattern(&rule.path_pattern, path) {
93                return rule.target_url.clone();
94            }
95        }
96
97        // If no rule matches, use the default target URL
98        if let Some(base_url) = &self.target_url {
99            base_url.clone()
100        } else {
101            path.to_string()
102        }
103    }
104
105    /// Strip the proxy prefix from a path
106    pub fn strip_prefix(&self, path: &str) -> String {
107        match &self.prefix {
108            Some(prefix) => {
109                if path.starts_with(prefix) {
110                    let stripped = path.strip_prefix(prefix).unwrap_or(path);
111                    // Ensure the result starts with a slash
112                    if stripped.starts_with('/') {
113                        stripped.to_string()
114                    } else {
115                        format!("/{}", stripped)
116                    }
117                } else {
118                    path.to_string()
119                }
120            }
121            None => path.to_string(), // No prefix to strip
122        }
123    }
124
125    /// Check if a path matches a pattern (supports wildcards)
126    fn path_matches_pattern(&self, pattern: &str, path: &str) -> bool {
127        if let Some(prefix) = pattern.strip_suffix("/*") {
128            path.starts_with(prefix)
129        } else {
130            path == pattern
131        }
132    }
133}
134
135impl Default for ProxyConfig {
136    fn default() -> Self {
137        Self {
138            enabled: false,
139            target_url: None,
140            timeout_seconds: 30,
141            follow_redirects: true,
142            headers: std::collections::HashMap::new(),
143            prefix: None,
144            passthrough_by_default: false,
145            rules: Vec::new(),
146        }
147    }
148}