Skip to main content

rs_zero/rest/
config.rs

1use std::time::Duration;
2
3/// JWT authorization configuration.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct AuthConfig {
6    /// HS256 secret used to validate bearer tokens.
7    pub secret: String,
8    /// Exact request paths that bypass token validation.
9    pub public_paths: Vec<String>,
10}
11
12impl AuthConfig {
13    /// Returns whether `path` is public.
14    pub fn is_public(&self, path: &str) -> bool {
15        self.public_paths.iter().any(|item| item == path)
16    }
17}
18
19/// REST service runtime configuration.
20#[derive(Debug, Clone)]
21pub struct RestConfig {
22    /// Service name used by logs and traces.
23    pub name: String,
24    /// Request timeout.
25    pub timeout: Duration,
26    /// Maximum accepted request body size.
27    pub max_body_bytes: usize,
28    /// Optional JWT authorization configuration.
29    pub auth: Option<AuthConfig>,
30    /// Default middleware controls.
31    pub middlewares: RestMiddlewareConfig,
32    /// Metrics registry used when metrics middleware is enabled.
33    #[cfg(feature = "observability")]
34    pub metrics_registry: Option<crate::observability::MetricsRegistry>,
35}
36
37impl Default for RestConfig {
38    fn default() -> Self {
39        Self {
40            name: "rs-zero".to_string(),
41            timeout: Duration::from_secs(5),
42            max_body_bytes: 1024 * 1024,
43            auth: None,
44            middlewares: RestMiddlewareConfig::default(),
45            #[cfg(feature = "observability")]
46            metrics_registry: None,
47        }
48    }
49}
50
51impl RestConfig {
52    /// Creates a REST config with production-oriented protection enabled.
53    pub fn production_defaults(name: impl Into<String>) -> Self {
54        Self {
55            name: name.into(),
56            middlewares: RestMiddlewareConfig {
57                resilience: RestResilienceConfig::production_defaults(),
58                metrics: RestMetricsConfig { enabled: true },
59            },
60            ..Self::default()
61        }
62    }
63
64    /// Creates a REST config with production-oriented protection and a Redis limiter enabled.
65    #[cfg(all(feature = "resil", feature = "cache-redis"))]
66    pub fn production_defaults_with_redis_limiter(
67        name: impl Into<String>,
68        limiter: RestRateLimiterConfig,
69    ) -> Self {
70        let mut config = Self::production_defaults(name);
71        config.middlewares.resilience.rate_limiter = limiter;
72        config
73    }
74
75    /// Creates a REST config with production-oriented protection enabled.
76    #[allow(deprecated)]
77    #[deprecated(note = "use production_defaults instead")]
78    pub fn go_zero_defaults(name: impl Into<String>) -> Self {
79        Self::production_defaults(name)
80    }
81}
82
83/// Controls optional REST middleware.
84#[derive(Debug, Clone, PartialEq, Eq, Default)]
85pub struct RestMiddlewareConfig {
86    /// Resilience middleware configuration.
87    pub resilience: RestResilienceConfig,
88    /// Metrics middleware configuration.
89    pub metrics: RestMetricsConfig,
90}
91
92/// REST resilience middleware configuration.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct RestResilienceConfig {
95    /// Whether route-level circuit breaking is enabled.
96    pub breaker_enabled: bool,
97    /// Consecutive failures that open a route breaker.
98    pub breaker_failure_threshold: u32,
99    /// Time before an open breaker allows a half-open trial call.
100    pub breaker_reset_timeout: Duration,
101    /// Whether the route breaker uses Google SRE style adaptive rejection.
102    pub breaker_sre_enabled: bool,
103    /// SRE breaker multiplier in millis. `1500` means `k = 1.5`.
104    pub breaker_sre_k_millis: u32,
105    /// Minimum total samples before SRE breaker rejection can start.
106    pub breaker_sre_protection: u64,
107    /// Optional maximum number of in-flight requests.
108    pub max_concurrency: Option<usize>,
109    /// Optional timeout overriding [`RestConfig::timeout`] for the default stack.
110    pub request_timeout: Option<Duration>,
111    /// Whether adaptive request shedding is enabled.
112    pub shedding_enabled: bool,
113    /// Maximum in-flight requests used by the adaptive shedder.
114    pub shedding_max_in_flight: Option<usize>,
115    /// Minimum request samples before latency-based shedding can reject.
116    pub shedding_min_request_count: u64,
117    /// Average latency threshold used by adaptive shedding.
118    pub shedding_max_latency: Duration,
119    /// CPU usage threshold used by adaptive shedding, where `1000` means 100%.
120    pub shedding_cpu_threshold_millis: u32,
121    /// Cool-off duration after a recent adaptive shedder drop.
122    pub shedding_cool_off: Duration,
123    /// Number of buckets used by adaptive shedder rolling windows.
124    pub shedding_window_buckets: usize,
125    /// Duration represented by each adaptive shedder bucket.
126    pub shedding_window_bucket_duration: Duration,
127    /// Optional Redis-backed REST limiter.
128    #[cfg(all(feature = "resil", feature = "cache-redis"))]
129    pub rate_limiter: RestRateLimiterConfig,
130}
131
132impl Default for RestResilienceConfig {
133    fn default() -> Self {
134        Self {
135            breaker_enabled: false,
136            breaker_failure_threshold: 5,
137            breaker_reset_timeout: Duration::from_secs(30),
138            breaker_sre_enabled: false,
139            breaker_sre_k_millis: 1500,
140            breaker_sre_protection: 5,
141            max_concurrency: None,
142            request_timeout: None,
143            shedding_enabled: false,
144            shedding_max_in_flight: None,
145            shedding_min_request_count: 20,
146            shedding_max_latency: Duration::from_millis(250),
147            shedding_cpu_threshold_millis: 900,
148            shedding_cool_off: Duration::from_secs(1),
149            shedding_window_buckets: 50,
150            shedding_window_bucket_duration: Duration::from_millis(100),
151            #[cfg(all(feature = "resil", feature = "cache-redis"))]
152            rate_limiter: RestRateLimiterConfig::default(),
153        }
154    }
155}
156
157impl RestResilienceConfig {
158    /// Returns a production-oriented resilience profile for production services.
159    pub fn production_defaults() -> Self {
160        Self {
161            breaker_enabled: true,
162            breaker_sre_enabled: true,
163            max_concurrency: Some(1024),
164            request_timeout: Some(Duration::from_secs(5)),
165            shedding_enabled: true,
166            shedding_max_in_flight: Some(1024),
167            shedding_min_request_count: 20,
168            shedding_max_latency: Duration::from_millis(250),
169            ..Self::default()
170        }
171    }
172
173    /// Returns a production-oriented resilience profile.
174    #[allow(deprecated)]
175    #[deprecated(note = "use production_defaults instead")]
176    pub fn go_zero_defaults() -> Self {
177        Self::production_defaults()
178    }
179}
180
181/// Redis-backed REST limiter selection.
182#[cfg(all(feature = "resil", feature = "cache-redis"))]
183#[derive(Debug, Clone, PartialEq, Eq, Default)]
184pub enum RestRateLimiterConfig {
185    /// No Redis limiter is applied.
186    #[default]
187    Disabled,
188    /// Token-bucket limiter backed by Redis.
189    RedisToken(crate::resil::RedisTokenLimiterConfig),
190    /// Fixed-window period limiter backed by Redis.
191    RedisPeriod(crate::resil::RedisPeriodLimiterConfig),
192}
193
194/// REST metrics middleware configuration.
195#[derive(Debug, Clone, PartialEq, Eq, Default)]
196pub struct RestMetricsConfig {
197    /// Whether metrics middleware should be applied by the default stack.
198    pub enabled: bool,
199}