1use std::{collections::HashMap, path::PathBuf};
10
11use serde::Deserialize;
12
13pub mod cors;
14pub mod env;
15pub mod error_sanitization;
16pub mod loader;
17pub mod metrics;
18pub mod pool_tuning;
19pub mod rate_limiting;
20#[cfg(test)]
21mod tests;
22pub mod tracing;
23pub mod validation;
24
25pub use cors::CorsConfig;
27pub use error_sanitization::{ErrorSanitizationConfig, ErrorSanitizer};
28pub use metrics::{LatencyTargets, MetricsConfig, SloConfig};
29#[allow(deprecated)] pub use pool_tuning::{PoolPressureMonitorConfig, PoolTuningConfig};
31pub use rate_limiting::{BackpressureConfig, RateLimitRule, RateLimitingConfig};
32pub use tracing::TracingConfig;
33
34#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
48pub struct UsagePersistenceConfig {
49 #[serde(default = "default_flush_interval_secs")]
53 pub flush_interval_secs: u64,
54}
55
56const fn default_flush_interval_secs() -> u64 {
57 60
58}
59
60#[derive(Debug, Clone, Deserialize)]
62pub struct RuntimeConfig {
63 pub server: HttpServerConfig,
65 pub database: DatabaseConfig,
67
68 #[serde(default)]
70 pub webhooks: HashMap<String, WebhookRouteConfig>,
71
72 #[serde(default)]
74 pub files: HashMap<String, FileConfig>,
75
76 #[serde(default)]
78 pub auth: Option<AuthConfig>,
79
80 #[serde(default)]
82 pub notifications: Option<NotificationsConfig>,
83
84 #[serde(default)]
86 pub observers: HashMap<String, ObserverConfig>,
87
88 #[serde(default)]
90 pub interceptors: HashMap<String, Vec<String>>,
91
92 #[serde(default)]
94 pub rate_limiting: Option<RateLimitingConfig>,
95
96 #[serde(default)]
98 pub cors: Option<CorsConfig>,
99
100 #[serde(default)]
102 pub metrics: Option<MetricsConfig>,
103
104 #[serde(default)]
106 pub tracing: Option<TracingConfig>,
107
108 #[serde(default)]
110 pub logging: Option<LoggingConfig>,
111
112 #[serde(default)]
114 pub storage: HashMap<String, StorageConfig>,
115
116 #[serde(default)]
118 pub search: Option<SearchConfig>,
119
120 #[serde(default)]
122 pub cache: Option<CacheConfig>,
123
124 #[serde(default)]
126 pub queues: Option<QueueConfig>,
127
128 #[serde(default)]
130 pub custom_endpoints: Option<CustomEndpointsConfig>,
131
132 #[serde(default)]
134 pub lifecycle: Option<LifecycleConfig>,
135}
136
137#[derive(Debug, Clone, Deserialize)]
139pub struct HttpServerConfig {
140 #[serde(default = "default_port")]
142 pub port: u16,
143
144 #[serde(default = "default_host")]
146 pub host: String,
147
148 #[serde(default)]
150 pub workers: Option<usize>,
151
152 #[serde(default)]
154 pub tls: Option<TlsConfig>,
155
156 #[serde(default)]
158 pub limits: Option<ServerLimitsConfig>,
159}
160
161const fn default_port() -> u16 {
162 4000
163}
164fn default_host() -> String {
165 "127.0.0.1".to_string()
166}
167
168#[derive(Debug, Clone, Deserialize)]
170pub struct TlsConfig {
171 pub cert_file: PathBuf,
173 pub key_file: PathBuf,
175}
176
177#[derive(Debug, Clone, Deserialize)]
179pub struct ServerLimitsConfig {
180 #[serde(default = "default_max_request_size")]
183 pub max_request_size: String,
184
185 #[serde(default = "default_request_timeout")]
187 pub request_timeout: String,
188
189 #[serde(default = "default_max_concurrent")]
191 pub max_concurrent_requests: usize,
192
193 #[serde(default = "default_max_queue_depth")]
195 pub max_queue_depth: usize,
196}
197
198fn default_max_request_size() -> String {
199 "10MB".to_string()
200}
201fn default_request_timeout() -> String {
202 "30s".to_string()
203}
204const fn default_max_concurrent() -> usize {
205 1000
206}
207const fn default_max_queue_depth() -> usize {
208 5000
209}
210
211#[derive(Debug, Clone, Deserialize)]
213pub struct DatabaseConfig {
214 pub url_env: String,
216
217 #[serde(default = "default_pool_size")]
219 pub pool_size: u32,
220
221 #[serde(default)]
223 pub pool_timeout: Option<String>,
224
225 #[serde(default)]
227 pub statement_timeout: Option<String>,
228
229 #[serde(default)]
231 pub replicas: Vec<ReplicaConfig>,
232
233 #[serde(default)]
235 pub health_check_interval: Option<String>,
236}
237
238const fn default_pool_size() -> u32 {
239 10
240}
241
242#[derive(Debug, Clone, Deserialize)]
244pub struct ReplicaConfig {
245 pub url_env: String,
247
248 #[serde(default = "default_weight")]
250 pub weight: u32,
251}
252
253const fn default_weight() -> u32 {
254 1
255}
256
257#[derive(Debug, Clone, Deserialize)]
259pub struct LifecycleConfig {
260 #[serde(default = "default_shutdown_timeout")]
262 pub shutdown_timeout: String,
263
264 #[serde(default = "default_shutdown_delay")]
266 pub shutdown_delay: String,
267
268 #[serde(default = "default_health_path")]
270 pub health_path: String,
271
272 #[serde(default = "default_ready_path")]
274 pub ready_path: String,
275}
276
277impl Default for LifecycleConfig {
278 fn default() -> Self {
279 Self {
280 shutdown_timeout: default_shutdown_timeout(),
281 shutdown_delay: default_shutdown_delay(),
282 health_path: default_health_path(),
283 ready_path: default_ready_path(),
284 }
285 }
286}
287
288fn default_shutdown_timeout() -> String {
289 "30s".to_string()
290}
291fn default_shutdown_delay() -> String {
292 "5s".to_string()
293}
294fn default_health_path() -> String {
295 "/health".to_string()
296}
297fn default_ready_path() -> String {
298 "/ready".to_string()
299}
300
301#[derive(Debug, Clone, serde::Serialize, Deserialize)]
303pub struct WebhookRouteConfig {
304 pub secret_env: String,
306 pub provider: String,
308 #[serde(default)]
310 pub path: Option<String>,
311}
312
313#[derive(Debug, Clone, Deserialize)]
315pub struct FileConfig {
316 pub storage: String,
318 pub max_size: String,
320 #[serde(default)]
322 pub path: Option<String>,
323}
324
325#[derive(Debug, Clone, Deserialize)]
327pub struct AuthConfig {
328 pub jwt: JwtConfig,
330 #[serde(default)]
332 pub providers: HashMap<String, OAuthProviderConfig>,
333 #[serde(default)]
335 pub callback_base_url: Option<String>,
336}
337
338#[derive(Debug, Clone, Deserialize)]
340pub struct JwtConfig {
341 pub secret_env: String,
343}
344
345#[derive(Debug, Clone, Deserialize)]
347pub struct OAuthProviderConfig {
348 pub provider_type: String,
350 pub client_id_env: String,
352 pub client_secret_env: String,
354 #[serde(default)]
356 pub issuer_url: Option<String>,
357}
358
359#[derive(Debug, Clone, Deserialize)]
361pub struct NotificationsConfig {}
362
363#[derive(Debug, Clone, Deserialize)]
365pub struct ObserverConfig {
366 pub entity: String,
368 pub events: Vec<String>,
370 pub actions: Vec<ActionConfig>,
372}
373
374#[derive(Debug, Clone, Deserialize)]
376pub struct ActionConfig {
377 #[serde(rename = "type")]
379 pub action_type: String,
380 #[serde(default)]
382 pub template: Option<String>,
383}
384
385#[derive(Debug, Clone, Deserialize)]
389pub struct LoggingConfig {}
390
391#[derive(Debug, Clone, Deserialize)]
393pub struct StorageConfig {
394 pub backend: String,
396 #[serde(default)]
398 pub bucket: Option<String>,
399 #[serde(default)]
401 pub path: Option<String>,
402 #[serde(default)]
404 pub region: Option<String>,
405 #[serde(default)]
408 pub endpoint: Option<String>,
409 #[serde(default)]
411 pub project_id: Option<String>,
412 #[serde(default)]
414 pub account_name: Option<String>,
415 #[serde(default = "default_max_upload_bytes")]
420 pub max_upload_bytes: usize,
421}
422
423const fn default_max_upload_bytes() -> usize {
424 100 * 1024 * 1024 }
426
427#[derive(Debug, Clone, Deserialize)]
429pub struct SearchConfig {}
430
431#[derive(Debug, Clone, Deserialize)]
433pub struct CacheConfig {}
434
435#[derive(Debug, Clone, Deserialize)]
437pub struct QueueConfig {}
438
439#[derive(Debug, Clone, Deserialize)]
441pub struct CustomEndpointsConfig {}
442
443impl HttpServerConfig {
448 #[must_use = "builder does nothing until .build() is called"]
450 pub fn builder() -> HttpServerConfigBuilder {
451 HttpServerConfigBuilder::default()
452 }
453}
454
455#[derive(Debug)]
457pub struct HttpServerConfigBuilder {
458 port: u16,
459 host: String,
460 workers: Option<usize>,
461 tls: Option<TlsConfig>,
462 limits: Option<ServerLimitsConfig>,
463}
464
465impl Default for HttpServerConfigBuilder {
466 fn default() -> Self {
467 Self {
468 port: default_port(),
469 host: default_host(),
470 workers: None,
471 tls: None,
472 limits: None,
473 }
474 }
475}
476
477impl HttpServerConfigBuilder {
478 #[must_use = "builder method returns modified builder"]
480 pub const fn port(mut self, port: u16) -> Self {
481 self.port = port;
482 self
483 }
484
485 pub fn host(mut self, host: impl Into<String>) -> Self {
487 self.host = host.into();
488 self
489 }
490
491 #[must_use = "builder method returns modified builder"]
493 pub const fn workers(mut self, workers: usize) -> Self {
494 self.workers = Some(workers);
495 self
496 }
497
498 #[must_use = "builder method returns modified builder"]
500 pub fn tls(mut self, tls: TlsConfig) -> Self {
501 self.tls = Some(tls);
502 self
503 }
504
505 #[must_use = "builder method returns modified builder"]
507 pub fn limits(mut self, limits: ServerLimitsConfig) -> Self {
508 self.limits = Some(limits);
509 self
510 }
511
512 #[must_use = "building a config that is not used has no effect"]
514 pub fn build(self) -> HttpServerConfig {
515 HttpServerConfig {
516 port: self.port,
517 host: self.host,
518 workers: self.workers,
519 tls: self.tls,
520 limits: self.limits,
521 }
522 }
523}
524
525impl ServerLimitsConfig {
526 #[must_use = "builder does nothing until .build() is called"]
528 pub fn builder() -> ServerLimitsConfigBuilder {
529 ServerLimitsConfigBuilder::default()
530 }
531}
532
533#[derive(Debug)]
535pub struct ServerLimitsConfigBuilder {
536 max_request_size: String,
537 request_timeout: String,
538 max_concurrent_requests: usize,
539 max_queue_depth: usize,
540}
541
542impl Default for ServerLimitsConfigBuilder {
543 fn default() -> Self {
544 Self {
545 max_request_size: default_max_request_size(),
546 request_timeout: default_request_timeout(),
547 max_concurrent_requests: default_max_concurrent(),
548 max_queue_depth: default_max_queue_depth(),
549 }
550 }
551}
552
553impl ServerLimitsConfigBuilder {
554 pub fn max_request_size(mut self, max_request_size: impl Into<String>) -> Self {
556 self.max_request_size = max_request_size.into();
557 self
558 }
559
560 pub fn request_timeout(mut self, request_timeout: impl Into<String>) -> Self {
562 self.request_timeout = request_timeout.into();
563 self
564 }
565
566 #[must_use = "builder method returns modified builder"]
568 pub const fn max_concurrent_requests(mut self, max_concurrent_requests: usize) -> Self {
569 self.max_concurrent_requests = max_concurrent_requests;
570 self
571 }
572
573 #[must_use = "builder method returns modified builder"]
575 pub const fn max_queue_depth(mut self, max_queue_depth: usize) -> Self {
576 self.max_queue_depth = max_queue_depth;
577 self
578 }
579
580 #[must_use = "building a config that is not used has no effect"]
582 pub fn build(self) -> ServerLimitsConfig {
583 ServerLimitsConfig {
584 max_request_size: self.max_request_size,
585 request_timeout: self.request_timeout,
586 max_concurrent_requests: self.max_concurrent_requests,
587 max_queue_depth: self.max_queue_depth,
588 }
589 }
590}
591
592impl DatabaseConfig {
593 #[must_use = "builder does nothing until .build() is called"]
595 pub fn builder() -> DatabaseConfigBuilder {
596 DatabaseConfigBuilder::default()
597 }
598}
599
600#[derive(Debug, Default)]
602pub struct DatabaseConfigBuilder {
603 url_env: Option<String>,
604 pool_size: u32,
605 pool_timeout: Option<String>,
606 statement_timeout: Option<String>,
607 replicas: Vec<ReplicaConfig>,
608 health_check_interval: Option<String>,
609}
610
611impl DatabaseConfigBuilder {
612 pub fn url_env(mut self, url_env: impl Into<String>) -> Self {
614 self.url_env = Some(url_env.into());
615 self
616 }
617
618 #[must_use = "builder method returns modified builder"]
620 pub const fn pool_size(mut self, pool_size: u32) -> Self {
621 self.pool_size = pool_size;
622 self
623 }
624
625 pub fn pool_timeout(mut self, pool_timeout: impl Into<String>) -> Self {
627 self.pool_timeout = Some(pool_timeout.into());
628 self
629 }
630
631 pub fn statement_timeout(mut self, statement_timeout: impl Into<String>) -> Self {
633 self.statement_timeout = Some(statement_timeout.into());
634 self
635 }
636
637 #[must_use = "builder method returns modified builder"]
639 pub fn replica(mut self, replica: ReplicaConfig) -> Self {
640 self.replicas.push(replica);
641 self
642 }
643
644 pub fn health_check_interval(mut self, health_check_interval: impl Into<String>) -> Self {
646 self.health_check_interval = Some(health_check_interval.into());
647 self
648 }
649
650 pub fn build(self) -> Result<DatabaseConfig, String> {
656 let url_env =
657 self.url_env.ok_or_else(|| "DatabaseConfig: url_env is required".to_string())?;
658 Ok(DatabaseConfig {
659 url_env,
660 pool_size: if self.pool_size == 0 {
661 default_pool_size()
662 } else {
663 self.pool_size
664 },
665 pool_timeout: self.pool_timeout,
666 statement_timeout: self.statement_timeout,
667 replicas: self.replicas,
668 health_check_interval: self.health_check_interval,
669 })
670 }
671}
672
673impl LifecycleConfig {
674 #[must_use = "builder does nothing until .build() is called"]
676 pub fn builder() -> LifecycleConfigBuilder {
677 LifecycleConfigBuilder::default()
678 }
679}
680
681#[derive(Debug)]
683pub struct LifecycleConfigBuilder {
684 shutdown_timeout: String,
685 shutdown_delay: String,
686 health_path: String,
687 ready_path: String,
688}
689
690impl Default for LifecycleConfigBuilder {
691 fn default() -> Self {
692 Self {
693 shutdown_timeout: default_shutdown_timeout(),
694 shutdown_delay: default_shutdown_delay(),
695 health_path: default_health_path(),
696 ready_path: default_ready_path(),
697 }
698 }
699}
700
701impl LifecycleConfigBuilder {
702 pub fn shutdown_timeout(mut self, shutdown_timeout: impl Into<String>) -> Self {
704 self.shutdown_timeout = shutdown_timeout.into();
705 self
706 }
707
708 pub fn shutdown_delay(mut self, shutdown_delay: impl Into<String>) -> Self {
710 self.shutdown_delay = shutdown_delay.into();
711 self
712 }
713
714 pub fn health_path(mut self, health_path: impl Into<String>) -> Self {
716 self.health_path = health_path.into();
717 self
718 }
719
720 pub fn ready_path(mut self, ready_path: impl Into<String>) -> Self {
722 self.ready_path = ready_path.into();
723 self
724 }
725
726 #[must_use = "building a config that is not used has no effect"]
728 pub fn build(self) -> LifecycleConfig {
729 LifecycleConfig {
730 shutdown_timeout: self.shutdown_timeout,
731 shutdown_delay: self.shutdown_delay,
732 health_path: self.health_path,
733 ready_path: self.ready_path,
734 }
735 }
736}