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 realtime: Option<RealtimeConfig>,
131
132 #[serde(default)]
134 pub custom_endpoints: Option<CustomEndpointsConfig>,
135
136 #[serde(default)]
138 pub lifecycle: Option<LifecycleConfig>,
139}
140
141#[derive(Debug, Clone, Deserialize)]
143pub struct HttpServerConfig {
144 #[serde(default = "default_port")]
146 pub port: u16,
147
148 #[serde(default = "default_host")]
150 pub host: String,
151
152 #[serde(default)]
154 pub workers: Option<usize>,
155
156 #[serde(default)]
158 pub tls: Option<TlsConfig>,
159
160 #[serde(default)]
162 pub limits: Option<ServerLimitsConfig>,
163}
164
165const fn default_port() -> u16 {
166 4000
167}
168fn default_host() -> String {
169 "127.0.0.1".to_string()
170}
171
172#[derive(Debug, Clone, Deserialize)]
174pub struct TlsConfig {
175 pub cert_file: PathBuf,
177 pub key_file: PathBuf,
179}
180
181#[derive(Debug, Clone, Deserialize)]
183pub struct ServerLimitsConfig {
184 #[serde(default = "default_max_request_size")]
187 pub max_request_size: String,
188
189 #[serde(default = "default_request_timeout")]
191 pub request_timeout: String,
192
193 #[serde(default = "default_max_concurrent")]
195 pub max_concurrent_requests: usize,
196
197 #[serde(default = "default_max_queue_depth")]
199 pub max_queue_depth: usize,
200}
201
202fn default_max_request_size() -> String {
203 "10MB".to_string()
204}
205fn default_request_timeout() -> String {
206 "30s".to_string()
207}
208const fn default_max_concurrent() -> usize {
209 1000
210}
211const fn default_max_queue_depth() -> usize {
212 5000
213}
214
215#[derive(Debug, Clone, Deserialize)]
217pub struct DatabaseConfig {
218 pub url_env: String,
220
221 #[serde(default = "default_pool_size")]
223 pub pool_size: u32,
224
225 #[serde(default)]
227 pub pool_timeout: Option<String>,
228
229 #[serde(default)]
231 pub statement_timeout: Option<String>,
232
233 #[serde(default)]
235 pub replicas: Vec<ReplicaConfig>,
236
237 #[serde(default)]
239 pub health_check_interval: Option<String>,
240}
241
242const fn default_pool_size() -> u32 {
243 10
244}
245
246#[derive(Debug, Clone, Deserialize)]
248pub struct ReplicaConfig {
249 pub url_env: String,
251
252 #[serde(default = "default_weight")]
254 pub weight: u32,
255}
256
257const fn default_weight() -> u32 {
258 1
259}
260
261#[derive(Debug, Clone, Deserialize)]
263pub struct LifecycleConfig {
264 #[serde(default = "default_shutdown_timeout")]
266 pub shutdown_timeout: String,
267
268 #[serde(default = "default_shutdown_delay")]
270 pub shutdown_delay: String,
271
272 #[serde(default = "default_health_path")]
274 pub health_path: String,
275
276 #[serde(default = "default_ready_path")]
278 pub ready_path: String,
279}
280
281impl Default for LifecycleConfig {
282 fn default() -> Self {
283 Self {
284 shutdown_timeout: default_shutdown_timeout(),
285 shutdown_delay: default_shutdown_delay(),
286 health_path: default_health_path(),
287 ready_path: default_ready_path(),
288 }
289 }
290}
291
292fn default_shutdown_timeout() -> String {
293 "30s".to_string()
294}
295fn default_shutdown_delay() -> String {
296 "5s".to_string()
297}
298fn default_health_path() -> String {
299 "/health".to_string()
300}
301fn default_ready_path() -> String {
302 "/ready".to_string()
303}
304
305#[derive(Debug, Clone, Deserialize)]
307pub struct WebhookRouteConfig {
308 pub secret_env: String,
310 pub provider: String,
312 #[serde(default)]
314 pub path: Option<String>,
315}
316
317#[derive(Debug, Clone, Deserialize)]
319pub struct FileConfig {
320 pub storage: String,
322 pub max_size: String,
324 #[serde(default)]
326 pub path: Option<String>,
327}
328
329#[derive(Debug, Clone, Deserialize)]
331pub struct AuthConfig {
332 pub jwt: JwtConfig,
334 #[serde(default)]
336 pub providers: HashMap<String, OAuthProviderConfig>,
337 #[serde(default)]
339 pub callback_base_url: Option<String>,
340}
341
342#[derive(Debug, Clone, Deserialize)]
344pub struct JwtConfig {
345 pub secret_env: String,
347}
348
349#[derive(Debug, Clone, Deserialize)]
351pub struct OAuthProviderConfig {
352 pub provider_type: String,
354 pub client_id_env: String,
356 pub client_secret_env: String,
358 #[serde(default)]
360 pub issuer_url: Option<String>,
361}
362
363#[derive(Debug, Clone, Deserialize)]
365pub struct NotificationsConfig {}
366
367#[derive(Debug, Clone, Deserialize)]
369pub struct ObserverConfig {
370 pub entity: String,
372 pub events: Vec<String>,
374 pub actions: Vec<ActionConfig>,
376}
377
378#[derive(Debug, Clone, Deserialize)]
380pub struct ActionConfig {
381 #[serde(rename = "type")]
383 pub action_type: String,
384 #[serde(default)]
386 pub template: Option<String>,
387}
388
389#[derive(Debug, Clone, Deserialize)]
393pub struct LoggingConfig {}
394
395#[derive(Debug, Clone, Deserialize)]
397pub struct StorageConfig {
398 pub backend: String,
400 #[serde(default)]
402 pub bucket: Option<String>,
403 #[serde(default)]
405 pub path: Option<String>,
406 #[serde(default)]
408 pub region: Option<String>,
409 #[serde(default)]
412 pub endpoint: Option<String>,
413 #[serde(default)]
415 pub project_id: Option<String>,
416 #[serde(default)]
418 pub account_name: Option<String>,
419 #[serde(default = "default_max_upload_bytes")]
424 pub max_upload_bytes: usize,
425}
426
427const fn default_max_upload_bytes() -> usize {
428 100 * 1024 * 1024 }
430
431#[derive(Debug, Clone, Deserialize)]
433pub struct SearchConfig {}
434
435#[derive(Debug, Clone, Deserialize)]
437pub struct CacheConfig {}
438
439#[derive(Debug, Clone, Deserialize)]
441pub struct QueueConfig {}
442
443#[derive(Debug, Clone, Deserialize)]
445pub struct RealtimeConfig {}
446
447#[derive(Debug, Clone, Deserialize)]
449pub struct CustomEndpointsConfig {}
450
451impl HttpServerConfig {
456 #[must_use = "builder does nothing until .build() is called"]
458 pub fn builder() -> HttpServerConfigBuilder {
459 HttpServerConfigBuilder::default()
460 }
461}
462
463#[derive(Debug)]
465pub struct HttpServerConfigBuilder {
466 port: u16,
467 host: String,
468 workers: Option<usize>,
469 tls: Option<TlsConfig>,
470 limits: Option<ServerLimitsConfig>,
471}
472
473impl Default for HttpServerConfigBuilder {
474 fn default() -> Self {
475 Self {
476 port: default_port(),
477 host: default_host(),
478 workers: None,
479 tls: None,
480 limits: None,
481 }
482 }
483}
484
485impl HttpServerConfigBuilder {
486 #[must_use = "builder method returns modified builder"]
488 pub const fn port(mut self, port: u16) -> Self {
489 self.port = port;
490 self
491 }
492
493 pub fn host(mut self, host: impl Into<String>) -> Self {
495 self.host = host.into();
496 self
497 }
498
499 #[must_use = "builder method returns modified builder"]
501 pub const fn workers(mut self, workers: usize) -> Self {
502 self.workers = Some(workers);
503 self
504 }
505
506 #[must_use = "builder method returns modified builder"]
508 pub fn tls(mut self, tls: TlsConfig) -> Self {
509 self.tls = Some(tls);
510 self
511 }
512
513 #[must_use = "builder method returns modified builder"]
515 pub fn limits(mut self, limits: ServerLimitsConfig) -> Self {
516 self.limits = Some(limits);
517 self
518 }
519
520 #[must_use = "building a config that is not used has no effect"]
522 pub fn build(self) -> HttpServerConfig {
523 HttpServerConfig {
524 port: self.port,
525 host: self.host,
526 workers: self.workers,
527 tls: self.tls,
528 limits: self.limits,
529 }
530 }
531}
532
533impl ServerLimitsConfig {
534 #[must_use = "builder does nothing until .build() is called"]
536 pub fn builder() -> ServerLimitsConfigBuilder {
537 ServerLimitsConfigBuilder::default()
538 }
539}
540
541#[derive(Debug)]
543pub struct ServerLimitsConfigBuilder {
544 max_request_size: String,
545 request_timeout: String,
546 max_concurrent_requests: usize,
547 max_queue_depth: usize,
548}
549
550impl Default for ServerLimitsConfigBuilder {
551 fn default() -> Self {
552 Self {
553 max_request_size: default_max_request_size(),
554 request_timeout: default_request_timeout(),
555 max_concurrent_requests: default_max_concurrent(),
556 max_queue_depth: default_max_queue_depth(),
557 }
558 }
559}
560
561impl ServerLimitsConfigBuilder {
562 pub fn max_request_size(mut self, max_request_size: impl Into<String>) -> Self {
564 self.max_request_size = max_request_size.into();
565 self
566 }
567
568 pub fn request_timeout(mut self, request_timeout: impl Into<String>) -> Self {
570 self.request_timeout = request_timeout.into();
571 self
572 }
573
574 #[must_use = "builder method returns modified builder"]
576 pub const fn max_concurrent_requests(mut self, max_concurrent_requests: usize) -> Self {
577 self.max_concurrent_requests = max_concurrent_requests;
578 self
579 }
580
581 #[must_use = "builder method returns modified builder"]
583 pub const fn max_queue_depth(mut self, max_queue_depth: usize) -> Self {
584 self.max_queue_depth = max_queue_depth;
585 self
586 }
587
588 #[must_use = "building a config that is not used has no effect"]
590 pub fn build(self) -> ServerLimitsConfig {
591 ServerLimitsConfig {
592 max_request_size: self.max_request_size,
593 request_timeout: self.request_timeout,
594 max_concurrent_requests: self.max_concurrent_requests,
595 max_queue_depth: self.max_queue_depth,
596 }
597 }
598}
599
600impl DatabaseConfig {
601 #[must_use = "builder does nothing until .build() is called"]
603 pub fn builder() -> DatabaseConfigBuilder {
604 DatabaseConfigBuilder::default()
605 }
606}
607
608#[derive(Debug, Default)]
610pub struct DatabaseConfigBuilder {
611 url_env: Option<String>,
612 pool_size: u32,
613 pool_timeout: Option<String>,
614 statement_timeout: Option<String>,
615 replicas: Vec<ReplicaConfig>,
616 health_check_interval: Option<String>,
617}
618
619impl DatabaseConfigBuilder {
620 pub fn url_env(mut self, url_env: impl Into<String>) -> Self {
622 self.url_env = Some(url_env.into());
623 self
624 }
625
626 #[must_use = "builder method returns modified builder"]
628 pub const fn pool_size(mut self, pool_size: u32) -> Self {
629 self.pool_size = pool_size;
630 self
631 }
632
633 pub fn pool_timeout(mut self, pool_timeout: impl Into<String>) -> Self {
635 self.pool_timeout = Some(pool_timeout.into());
636 self
637 }
638
639 pub fn statement_timeout(mut self, statement_timeout: impl Into<String>) -> Self {
641 self.statement_timeout = Some(statement_timeout.into());
642 self
643 }
644
645 #[must_use = "builder method returns modified builder"]
647 pub fn replica(mut self, replica: ReplicaConfig) -> Self {
648 self.replicas.push(replica);
649 self
650 }
651
652 pub fn health_check_interval(mut self, health_check_interval: impl Into<String>) -> Self {
654 self.health_check_interval = Some(health_check_interval.into());
655 self
656 }
657
658 pub fn build(self) -> Result<DatabaseConfig, String> {
664 let url_env =
665 self.url_env.ok_or_else(|| "DatabaseConfig: url_env is required".to_string())?;
666 Ok(DatabaseConfig {
667 url_env,
668 pool_size: if self.pool_size == 0 {
669 default_pool_size()
670 } else {
671 self.pool_size
672 },
673 pool_timeout: self.pool_timeout,
674 statement_timeout: self.statement_timeout,
675 replicas: self.replicas,
676 health_check_interval: self.health_check_interval,
677 })
678 }
679}
680
681impl LifecycleConfig {
682 #[must_use = "builder does nothing until .build() is called"]
684 pub fn builder() -> LifecycleConfigBuilder {
685 LifecycleConfigBuilder::default()
686 }
687}
688
689#[derive(Debug)]
691pub struct LifecycleConfigBuilder {
692 shutdown_timeout: String,
693 shutdown_delay: String,
694 health_path: String,
695 ready_path: String,
696}
697
698impl Default for LifecycleConfigBuilder {
699 fn default() -> Self {
700 Self {
701 shutdown_timeout: default_shutdown_timeout(),
702 shutdown_delay: default_shutdown_delay(),
703 health_path: default_health_path(),
704 ready_path: default_ready_path(),
705 }
706 }
707}
708
709impl LifecycleConfigBuilder {
710 pub fn shutdown_timeout(mut self, shutdown_timeout: impl Into<String>) -> Self {
712 self.shutdown_timeout = shutdown_timeout.into();
713 self
714 }
715
716 pub fn shutdown_delay(mut self, shutdown_delay: impl Into<String>) -> Self {
718 self.shutdown_delay = shutdown_delay.into();
719 self
720 }
721
722 pub fn health_path(mut self, health_path: impl Into<String>) -> Self {
724 self.health_path = health_path.into();
725 self
726 }
727
728 pub fn ready_path(mut self, ready_path: impl Into<String>) -> Self {
730 self.ready_path = ready_path.into();
731 self
732 }
733
734 #[must_use = "building a config that is not used has no effect"]
736 pub fn build(self) -> LifecycleConfig {
737 LifecycleConfig {
738 shutdown_timeout: self.shutdown_timeout,
739 shutdown_delay: self.shutdown_delay,
740 health_path: self.health_path,
741 ready_path: self.ready_path,
742 }
743 }
744}