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)]
411 pub endpoint: Option<String>,
412 #[serde(default)]
414 pub project_id: Option<String>,
415 #[serde(default)]
417 pub account_name: Option<String>,
418 #[serde(default = "default_max_upload_bytes")]
423 pub max_upload_bytes: usize,
424}
425
426const fn default_max_upload_bytes() -> usize {
427 100 * 1024 * 1024 }
429
430#[derive(Debug, Clone, Deserialize)]
432pub struct SearchConfig {}
433
434#[derive(Debug, Clone, Deserialize)]
436pub struct CacheConfig {}
437
438#[derive(Debug, Clone, Deserialize)]
440pub struct QueueConfig {}
441
442#[derive(Debug, Clone, Deserialize)]
444pub struct RealtimeConfig {}
445
446#[derive(Debug, Clone, Deserialize)]
448pub struct CustomEndpointsConfig {}
449
450impl HttpServerConfig {
455 #[must_use = "builder does nothing until .build() is called"]
457 pub fn builder() -> HttpServerConfigBuilder {
458 HttpServerConfigBuilder::default()
459 }
460}
461
462#[derive(Debug)]
464pub struct HttpServerConfigBuilder {
465 port: u16,
466 host: String,
467 workers: Option<usize>,
468 tls: Option<TlsConfig>,
469 limits: Option<ServerLimitsConfig>,
470}
471
472impl Default for HttpServerConfigBuilder {
473 fn default() -> Self {
474 Self {
475 port: default_port(),
476 host: default_host(),
477 workers: None,
478 tls: None,
479 limits: None,
480 }
481 }
482}
483
484impl HttpServerConfigBuilder {
485 #[must_use = "builder method returns modified builder"]
487 pub const fn port(mut self, port: u16) -> Self {
488 self.port = port;
489 self
490 }
491
492 pub fn host(mut self, host: impl Into<String>) -> Self {
494 self.host = host.into();
495 self
496 }
497
498 #[must_use = "builder method returns modified builder"]
500 pub const fn workers(mut self, workers: usize) -> Self {
501 self.workers = Some(workers);
502 self
503 }
504
505 #[must_use = "builder method returns modified builder"]
507 pub fn tls(mut self, tls: TlsConfig) -> Self {
508 self.tls = Some(tls);
509 self
510 }
511
512 #[must_use = "builder method returns modified builder"]
514 pub fn limits(mut self, limits: ServerLimitsConfig) -> Self {
515 self.limits = Some(limits);
516 self
517 }
518
519 #[must_use = "building a config that is not used has no effect"]
521 pub fn build(self) -> HttpServerConfig {
522 HttpServerConfig {
523 port: self.port,
524 host: self.host,
525 workers: self.workers,
526 tls: self.tls,
527 limits: self.limits,
528 }
529 }
530}
531
532impl ServerLimitsConfig {
533 #[must_use = "builder does nothing until .build() is called"]
535 pub fn builder() -> ServerLimitsConfigBuilder {
536 ServerLimitsConfigBuilder::default()
537 }
538}
539
540#[derive(Debug)]
542pub struct ServerLimitsConfigBuilder {
543 max_request_size: String,
544 request_timeout: String,
545 max_concurrent_requests: usize,
546 max_queue_depth: usize,
547}
548
549impl Default for ServerLimitsConfigBuilder {
550 fn default() -> Self {
551 Self {
552 max_request_size: default_max_request_size(),
553 request_timeout: default_request_timeout(),
554 max_concurrent_requests: default_max_concurrent(),
555 max_queue_depth: default_max_queue_depth(),
556 }
557 }
558}
559
560impl ServerLimitsConfigBuilder {
561 pub fn max_request_size(mut self, max_request_size: impl Into<String>) -> Self {
563 self.max_request_size = max_request_size.into();
564 self
565 }
566
567 pub fn request_timeout(mut self, request_timeout: impl Into<String>) -> Self {
569 self.request_timeout = request_timeout.into();
570 self
571 }
572
573 #[must_use = "builder method returns modified builder"]
575 pub const fn max_concurrent_requests(mut self, max_concurrent_requests: usize) -> Self {
576 self.max_concurrent_requests = max_concurrent_requests;
577 self
578 }
579
580 #[must_use = "builder method returns modified builder"]
582 pub const fn max_queue_depth(mut self, max_queue_depth: usize) -> Self {
583 self.max_queue_depth = max_queue_depth;
584 self
585 }
586
587 #[must_use = "building a config that is not used has no effect"]
589 pub fn build(self) -> ServerLimitsConfig {
590 ServerLimitsConfig {
591 max_request_size: self.max_request_size,
592 request_timeout: self.request_timeout,
593 max_concurrent_requests: self.max_concurrent_requests,
594 max_queue_depth: self.max_queue_depth,
595 }
596 }
597}
598
599impl DatabaseConfig {
600 #[must_use = "builder does nothing until .build() is called"]
602 pub fn builder() -> DatabaseConfigBuilder {
603 DatabaseConfigBuilder::default()
604 }
605}
606
607#[derive(Debug, Default)]
609pub struct DatabaseConfigBuilder {
610 url_env: Option<String>,
611 pool_size: u32,
612 pool_timeout: Option<String>,
613 statement_timeout: Option<String>,
614 replicas: Vec<ReplicaConfig>,
615 health_check_interval: Option<String>,
616}
617
618impl DatabaseConfigBuilder {
619 pub fn url_env(mut self, url_env: impl Into<String>) -> Self {
621 self.url_env = Some(url_env.into());
622 self
623 }
624
625 #[must_use = "builder method returns modified builder"]
627 pub const fn pool_size(mut self, pool_size: u32) -> Self {
628 self.pool_size = pool_size;
629 self
630 }
631
632 pub fn pool_timeout(mut self, pool_timeout: impl Into<String>) -> Self {
634 self.pool_timeout = Some(pool_timeout.into());
635 self
636 }
637
638 pub fn statement_timeout(mut self, statement_timeout: impl Into<String>) -> Self {
640 self.statement_timeout = Some(statement_timeout.into());
641 self
642 }
643
644 #[must_use = "builder method returns modified builder"]
646 pub fn replica(mut self, replica: ReplicaConfig) -> Self {
647 self.replicas.push(replica);
648 self
649 }
650
651 pub fn health_check_interval(mut self, health_check_interval: impl Into<String>) -> Self {
653 self.health_check_interval = Some(health_check_interval.into());
654 self
655 }
656
657 pub fn build(self) -> Result<DatabaseConfig, String> {
663 let url_env =
664 self.url_env.ok_or_else(|| "DatabaseConfig: url_env is required".to_string())?;
665 Ok(DatabaseConfig {
666 url_env,
667 pool_size: if self.pool_size == 0 {
668 default_pool_size()
669 } else {
670 self.pool_size
671 },
672 pool_timeout: self.pool_timeout,
673 statement_timeout: self.statement_timeout,
674 replicas: self.replicas,
675 health_check_interval: self.health_check_interval,
676 })
677 }
678}
679
680impl LifecycleConfig {
681 #[must_use = "builder does nothing until .build() is called"]
683 pub fn builder() -> LifecycleConfigBuilder {
684 LifecycleConfigBuilder::default()
685 }
686}
687
688#[derive(Debug)]
690pub struct LifecycleConfigBuilder {
691 shutdown_timeout: String,
692 shutdown_delay: String,
693 health_path: String,
694 ready_path: String,
695}
696
697impl Default for LifecycleConfigBuilder {
698 fn default() -> Self {
699 Self {
700 shutdown_timeout: default_shutdown_timeout(),
701 shutdown_delay: default_shutdown_delay(),
702 health_path: default_health_path(),
703 ready_path: default_ready_path(),
704 }
705 }
706}
707
708impl LifecycleConfigBuilder {
709 pub fn shutdown_timeout(mut self, shutdown_timeout: impl Into<String>) -> Self {
711 self.shutdown_timeout = shutdown_timeout.into();
712 self
713 }
714
715 pub fn shutdown_delay(mut self, shutdown_delay: impl Into<String>) -> Self {
717 self.shutdown_delay = shutdown_delay.into();
718 self
719 }
720
721 pub fn health_path(mut self, health_path: impl Into<String>) -> Self {
723 self.health_path = health_path.into();
724 self
725 }
726
727 pub fn ready_path(mut self, ready_path: impl Into<String>) -> Self {
729 self.ready_path = ready_path.into();
730 self
731 }
732
733 #[must_use = "building a config that is not used has no effect"]
735 pub fn build(self) -> LifecycleConfig {
736 LifecycleConfig {
737 shutdown_timeout: self.shutdown_timeout,
738 shutdown_delay: self.shutdown_delay,
739 health_path: self.health_path,
740 ready_path: self.ready_path,
741 }
742 }
743}