Skip to main content

postrust_proxy/config/
types.rs

1//! Configuration types for the proxy.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use uuid::Uuid;
6
7/// Main proxy configuration.
8#[derive(Clone, Debug, Serialize, Deserialize, Default)]
9pub struct ProxyConfig {
10    /// Server settings
11    #[serde(default)]
12    pub server: ServerConfig,
13
14    /// TLS/ACME settings
15    #[serde(default)]
16    pub tls: TlsConfig,
17
18    /// Default rate limiting settings
19    #[serde(default)]
20    pub rate_limit: RateLimitDefaults,
21
22    /// Routes (for file-based config)
23    #[serde(default)]
24    pub routes: Vec<Route>,
25
26    /// Upstreams (for file-based config)
27    #[serde(default)]
28    pub upstreams: Vec<Upstream>,
29}
30
31/// Server configuration.
32#[derive(Clone, Debug, Serialize, Deserialize)]
33pub struct ServerConfig {
34    /// Listen address for HTTP
35    #[serde(default = "default_http_host")]
36    pub http_host: String,
37
38    /// Listen port for HTTP
39    #[serde(default = "default_http_port")]
40    pub http_port: u16,
41
42    /// Listen address for HTTPS
43    #[serde(default = "default_https_host")]
44    pub https_host: String,
45
46    /// Listen port for HTTPS
47    #[serde(default = "default_https_port")]
48    pub https_port: u16,
49
50    /// Enable HTTPS listener
51    #[serde(default)]
52    pub https_enabled: bool,
53
54    /// Database-backed config enabled
55    #[serde(default = "default_true")]
56    pub database_config: bool,
57
58    /// Config file path (for file-based bootstrap)
59    pub config_file: Option<String>,
60
61    /// Enable file watcher for hot-reload
62    #[serde(default)]
63    pub watch_config_file: bool,
64}
65
66impl Default for ServerConfig {
67    fn default() -> Self {
68        Self {
69            http_host: default_http_host(),
70            http_port: default_http_port(),
71            https_host: default_https_host(),
72            https_port: default_https_port(),
73            https_enabled: false,
74            database_config: true,
75            config_file: None,
76            watch_config_file: false,
77        }
78    }
79}
80
81/// TLS configuration.
82#[derive(Clone, Debug, Serialize, Deserialize)]
83pub struct TlsConfig {
84    /// Enable ACME (Let's Encrypt)
85    #[serde(default)]
86    pub acme_enabled: bool,
87
88    /// ACME directory URL
89    #[serde(default = "default_acme_directory")]
90    pub acme_directory: String,
91
92    /// ACME contact email
93    pub acme_email: Option<String>,
94
95    /// Certificate storage directory
96    #[serde(default = "default_cert_dir")]
97    pub cert_dir: String,
98
99    /// Use staging ACME server
100    #[serde(default)]
101    pub acme_staging: bool,
102}
103
104impl Default for TlsConfig {
105    fn default() -> Self {
106        Self {
107            acme_enabled: false,
108            acme_directory: default_acme_directory(),
109            acme_email: None,
110            cert_dir: default_cert_dir(),
111            acme_staging: false,
112        }
113    }
114}
115
116/// Default rate limiting settings.
117#[derive(Clone, Debug, Serialize, Deserialize)]
118pub struct RateLimitDefaults {
119    /// Default requests per window
120    #[serde(default = "default_rate_limit_requests")]
121    pub requests: u32,
122
123    /// Default window size in seconds
124    #[serde(default = "default_rate_limit_window")]
125    pub window_secs: u32,
126
127    /// Burst allowance
128    #[serde(default = "default_burst")]
129    pub burst: u32,
130}
131
132impl Default for RateLimitDefaults {
133    fn default() -> Self {
134        Self {
135            requests: default_rate_limit_requests(),
136            window_secs: default_rate_limit_window(),
137            burst: default_burst(),
138        }
139    }
140}
141
142/// A proxy route configuration.
143#[derive(Clone, Debug, Serialize, Deserialize)]
144pub struct Route {
145    /// Route ID (database)
146    pub id: Option<Uuid>,
147
148    /// Route name
149    pub name: String,
150
151    /// Description
152    pub description: Option<String>,
153
154    /// Matching criteria
155    #[serde(default, rename = "match")]
156    pub match_: RouteMatch,
157
158    /// Priority (higher = matched first)
159    #[serde(default = "default_priority")]
160    pub priority: i32,
161
162    /// Upstream name or ID
163    pub upstream: String,
164
165    /// Strip matched path prefix
166    #[serde(default)]
167    pub strip_path: bool,
168
169    /// Headers to add to proxied requests
170    #[serde(default)]
171    pub add_headers: HashMap<String, String>,
172
173    /// Headers to remove from proxied requests
174    #[serde(default)]
175    pub remove_headers: Vec<String>,
176
177    /// Rate limiting for this route
178    pub rate_limit: Option<RouteRateLimit>,
179
180    /// Request timeout in seconds
181    #[serde(default = "default_timeout")]
182    pub timeout_secs: u32,
183
184    /// Retry count on failure
185    #[serde(default)]
186    pub retry_count: u32,
187
188    /// Route enabled
189    #[serde(default = "default_true")]
190    pub enabled: bool,
191}
192
193/// Route matching criteria.
194#[derive(Clone, Debug, Default, Serialize, Deserialize)]
195pub struct RouteMatch {
196    /// Host pattern (supports wildcards)
197    pub host: Option<String>,
198
199    /// Path pattern
200    pub path: Option<String>,
201
202    /// Path matching type
203    #[serde(default)]
204    pub path_type: PathMatchType,
205
206    /// Headers to match
207    #[serde(default)]
208    pub headers: HashMap<String, String>,
209
210    /// HTTP methods to match
211    pub methods: Option<Vec<String>>,
212}
213
214/// Path matching type.
215#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
216#[serde(rename_all = "lowercase")]
217pub enum PathMatchType {
218    /// Prefix matching (default)
219    #[default]
220    Prefix,
221    /// Exact matching
222    Exact,
223    /// Regex matching
224    Regex,
225}
226
227/// Per-route rate limiting.
228#[derive(Clone, Debug, Serialize, Deserialize)]
229pub struct RouteRateLimit {
230    /// Requests per window
231    pub requests: u32,
232    /// Window size in seconds
233    pub window_secs: u32,
234    /// Rate limit key
235    #[serde(default)]
236    pub key: RateLimitKey,
237}
238
239/// Rate limit key type.
240#[derive(Clone, Debug, Default, Serialize, Deserialize)]
241#[serde(rename_all = "snake_case")]
242pub enum RateLimitKey {
243    /// Rate limit by client IP
244    #[default]
245    ClientIp,
246    /// Rate limit by header value
247    Header(String),
248    /// Rate limit by route (global for route)
249    Route,
250}
251
252/// An upstream (group of backend servers).
253#[derive(Clone, Debug, Serialize, Deserialize)]
254pub struct Upstream {
255    /// Upstream ID (database)
256    pub id: Option<Uuid>,
257
258    /// Upstream name
259    pub name: String,
260
261    /// Description
262    pub description: Option<String>,
263
264    /// Load balancing strategy
265    #[serde(default)]
266    pub lb_strategy: LoadBalanceStrategy,
267
268    /// Backend servers
269    #[serde(default)]
270    pub backends: Vec<Backend>,
271
272    /// Health check configuration
273    #[serde(default)]
274    pub health_check: HealthCheckConfig,
275
276    /// Upstream enabled
277    #[serde(default = "default_true")]
278    pub enabled: bool,
279}
280
281/// Load balancing strategy.
282#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
283#[serde(rename_all = "snake_case")]
284pub enum LoadBalanceStrategy {
285    /// Round-robin (default)
286    #[default]
287    RoundRobin,
288    /// Least connections
289    LeastConnections,
290    /// Weighted
291    Weighted,
292    /// Random
293    Random,
294    /// Sticky (cookie-based)
295    Sticky,
296}
297
298/// A backend server.
299#[derive(Clone, Debug, Serialize, Deserialize)]
300pub struct Backend {
301    /// Backend ID (database)
302    pub id: Option<Uuid>,
303
304    /// Server address (host:port)
305    pub address: String,
306
307    /// HTTP or HTTPS
308    #[serde(default = "default_scheme")]
309    pub scheme: String,
310
311    /// Weight for weighted load balancing
312    #[serde(default = "default_weight")]
313    pub weight: u32,
314
315    /// Backend enabled
316    #[serde(default = "default_true")]
317    pub enabled: bool,
318}
319
320/// Health check configuration.
321#[derive(Clone, Debug, Serialize, Deserialize)]
322pub struct HealthCheckConfig {
323    /// Health check enabled
324    #[serde(default = "default_true")]
325    pub enabled: bool,
326
327    /// Health check path
328    #[serde(default = "default_health_path")]
329    pub path: String,
330
331    /// Check interval in seconds
332    #[serde(default = "default_health_interval")]
333    pub interval_secs: u32,
334
335    /// Check timeout in seconds
336    #[serde(default = "default_health_timeout")]
337    pub timeout_secs: u32,
338
339    /// Healthy threshold (consecutive successes)
340    #[serde(default = "default_healthy_threshold")]
341    pub healthy_threshold: u32,
342
343    /// Unhealthy threshold (consecutive failures)
344    #[serde(default = "default_unhealthy_threshold")]
345    pub unhealthy_threshold: u32,
346}
347
348impl Default for HealthCheckConfig {
349    fn default() -> Self {
350        Self {
351            enabled: true,
352            path: default_health_path(),
353            interval_secs: default_health_interval(),
354            timeout_secs: default_health_timeout(),
355            healthy_threshold: default_healthy_threshold(),
356            unhealthy_threshold: default_unhealthy_threshold(),
357        }
358    }
359}
360
361// Default value functions
362fn default_http_host() -> String {
363    "0.0.0.0".into()
364}
365fn default_http_port() -> u16 {
366    8080
367}
368fn default_https_host() -> String {
369    "0.0.0.0".into()
370}
371fn default_https_port() -> u16 {
372    8443
373}
374fn default_true() -> bool {
375    true
376}
377fn default_acme_directory() -> String {
378    "https://acme-v02.api.letsencrypt.org/directory".into()
379}
380fn default_cert_dir() -> String {
381    "./certs".into()
382}
383fn default_rate_limit_requests() -> u32 {
384    1000
385}
386fn default_rate_limit_window() -> u32 {
387    60
388}
389fn default_burst() -> u32 {
390    50
391}
392fn default_priority() -> i32 {
393    100
394}
395fn default_timeout() -> u32 {
396    30
397}
398fn default_scheme() -> String {
399    "http".into()
400}
401fn default_weight() -> u32 {
402    100
403}
404fn default_health_path() -> String {
405    "/health".into()
406}
407fn default_health_interval() -> u32 {
408    10
409}
410fn default_health_timeout() -> u32 {
411    5
412}
413fn default_healthy_threshold() -> u32 {
414    2
415}
416fn default_unhealthy_threshold() -> u32 {
417    3
418}
419
420/// ACME configuration for automatic certificate management.
421#[derive(Clone, Debug, Serialize, Deserialize, Default)]
422pub struct AcmeConfig {
423    /// ACME enabled
424    #[serde(default)]
425    pub enabled: bool,
426
427    /// Contact email
428    pub email: Option<String>,
429
430    /// Use staging server
431    #[serde(default)]
432    pub staging: bool,
433
434    /// Domains to request certificates for
435    #[serde(default)]
436    pub domains: Vec<String>,
437}