use std::{collections::HashMap, path::PathBuf};
use serde::Deserialize;
pub mod cors;
pub mod env;
pub mod error_sanitization;
pub mod loader;
pub mod metrics;
pub mod pool_tuning;
pub mod rate_limiting;
#[cfg(test)]
mod tests;
pub mod tracing;
pub mod validation;
pub use cors::CorsConfig;
pub use error_sanitization::{ErrorSanitizationConfig, ErrorSanitizer};
pub use metrics::{LatencyTargets, MetricsConfig, SloConfig};
#[allow(deprecated)] pub use pool_tuning::{PoolPressureMonitorConfig, PoolTuningConfig};
pub use rate_limiting::{BackpressureConfig, RateLimitRule, RateLimitingConfig};
pub use tracing::TracingConfig;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct UsagePersistenceConfig {
#[serde(default = "default_flush_interval_secs")]
pub flush_interval_secs: u64,
}
const fn default_flush_interval_secs() -> u64 {
60
}
#[derive(Debug, Clone, Deserialize)]
pub struct RuntimeConfig {
pub server: HttpServerConfig,
pub database: DatabaseConfig,
#[serde(default)]
pub webhooks: HashMap<String, WebhookRouteConfig>,
#[serde(default)]
pub files: HashMap<String, FileConfig>,
#[serde(default)]
pub auth: Option<AuthConfig>,
#[serde(default)]
pub notifications: Option<NotificationsConfig>,
#[serde(default)]
pub observers: HashMap<String, ObserverConfig>,
#[serde(default)]
pub interceptors: HashMap<String, Vec<String>>,
#[serde(default)]
pub rate_limiting: Option<RateLimitingConfig>,
#[serde(default)]
pub cors: Option<CorsConfig>,
#[serde(default)]
pub metrics: Option<MetricsConfig>,
#[serde(default)]
pub tracing: Option<TracingConfig>,
#[serde(default)]
pub logging: Option<LoggingConfig>,
#[serde(default)]
pub storage: HashMap<String, StorageConfig>,
#[serde(default)]
pub search: Option<SearchConfig>,
#[serde(default)]
pub cache: Option<CacheConfig>,
#[serde(default)]
pub queues: Option<QueueConfig>,
#[serde(default)]
pub custom_endpoints: Option<CustomEndpointsConfig>,
#[serde(default)]
pub lifecycle: Option<LifecycleConfig>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct HttpServerConfig {
#[serde(default = "default_port")]
pub port: u16,
#[serde(default = "default_host")]
pub host: String,
#[serde(default)]
pub workers: Option<usize>,
#[serde(default)]
pub tls: Option<TlsConfig>,
#[serde(default)]
pub limits: Option<ServerLimitsConfig>,
}
const fn default_port() -> u16 {
4000
}
fn default_host() -> String {
"127.0.0.1".to_string()
}
#[derive(Debug, Clone, Deserialize)]
pub struct TlsConfig {
pub cert_file: PathBuf,
pub key_file: PathBuf,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ServerLimitsConfig {
#[serde(default = "default_max_request_size")]
pub max_request_size: String,
#[serde(default = "default_request_timeout")]
pub request_timeout: String,
#[serde(default = "default_max_concurrent")]
pub max_concurrent_requests: usize,
#[serde(default = "default_max_queue_depth")]
pub max_queue_depth: usize,
}
fn default_max_request_size() -> String {
"10MB".to_string()
}
fn default_request_timeout() -> String {
"30s".to_string()
}
const fn default_max_concurrent() -> usize {
1000
}
const fn default_max_queue_depth() -> usize {
5000
}
#[derive(Debug, Clone, Deserialize)]
pub struct DatabaseConfig {
pub url_env: String,
#[serde(default = "default_pool_size")]
pub pool_size: u32,
#[serde(default)]
pub pool_timeout: Option<String>,
#[serde(default)]
pub statement_timeout: Option<String>,
#[serde(default)]
pub replicas: Vec<ReplicaConfig>,
#[serde(default)]
pub health_check_interval: Option<String>,
}
const fn default_pool_size() -> u32 {
10
}
#[derive(Debug, Clone, Deserialize)]
pub struct ReplicaConfig {
pub url_env: String,
#[serde(default = "default_weight")]
pub weight: u32,
}
const fn default_weight() -> u32 {
1
}
#[derive(Debug, Clone, Deserialize)]
pub struct LifecycleConfig {
#[serde(default = "default_shutdown_timeout")]
pub shutdown_timeout: String,
#[serde(default = "default_shutdown_delay")]
pub shutdown_delay: String,
#[serde(default = "default_health_path")]
pub health_path: String,
#[serde(default = "default_ready_path")]
pub ready_path: String,
}
impl Default for LifecycleConfig {
fn default() -> Self {
Self {
shutdown_timeout: default_shutdown_timeout(),
shutdown_delay: default_shutdown_delay(),
health_path: default_health_path(),
ready_path: default_ready_path(),
}
}
}
fn default_shutdown_timeout() -> String {
"30s".to_string()
}
fn default_shutdown_delay() -> String {
"5s".to_string()
}
fn default_health_path() -> String {
"/health".to_string()
}
fn default_ready_path() -> String {
"/ready".to_string()
}
#[derive(Debug, Clone, serde::Serialize, Deserialize)]
pub struct WebhookRouteConfig {
pub secret_env: String,
pub provider: String,
#[serde(default)]
pub path: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FileConfig {
pub storage: String,
pub max_size: String,
#[serde(default)]
pub path: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AuthConfig {
pub jwt: JwtConfig,
#[serde(default)]
pub providers: HashMap<String, OAuthProviderConfig>,
#[serde(default)]
pub callback_base_url: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JwtConfig {
pub secret_env: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OAuthProviderConfig {
pub provider_type: String,
pub client_id_env: String,
pub client_secret_env: String,
#[serde(default)]
pub issuer_url: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NotificationsConfig {}
#[derive(Debug, Clone, Deserialize)]
pub struct ObserverConfig {
pub entity: String,
pub events: Vec<String>,
pub actions: Vec<ActionConfig>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ActionConfig {
#[serde(rename = "type")]
pub action_type: String,
#[serde(default)]
pub template: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct LoggingConfig {}
#[derive(Debug, Clone, Deserialize)]
pub struct StorageConfig {
pub backend: String,
#[serde(default)]
pub bucket: Option<String>,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub region: Option<String>,
#[serde(default)]
pub endpoint: Option<String>,
#[serde(default)]
pub project_id: Option<String>,
#[serde(default)]
pub account_name: Option<String>,
#[serde(default = "default_max_upload_bytes")]
pub max_upload_bytes: usize,
}
const fn default_max_upload_bytes() -> usize {
100 * 1024 * 1024 }
#[derive(Debug, Clone, Deserialize)]
pub struct SearchConfig {}
#[derive(Debug, Clone, Deserialize)]
pub struct CacheConfig {}
#[derive(Debug, Clone, Deserialize)]
pub struct QueueConfig {}
#[derive(Debug, Clone, Deserialize)]
pub struct CustomEndpointsConfig {}
impl HttpServerConfig {
#[must_use = "builder does nothing until .build() is called"]
pub fn builder() -> HttpServerConfigBuilder {
HttpServerConfigBuilder::default()
}
}
#[derive(Debug)]
pub struct HttpServerConfigBuilder {
port: u16,
host: String,
workers: Option<usize>,
tls: Option<TlsConfig>,
limits: Option<ServerLimitsConfig>,
}
impl Default for HttpServerConfigBuilder {
fn default() -> Self {
Self {
port: default_port(),
host: default_host(),
workers: None,
tls: None,
limits: None,
}
}
}
impl HttpServerConfigBuilder {
#[must_use = "builder method returns modified builder"]
pub const fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = host.into();
self
}
#[must_use = "builder method returns modified builder"]
pub const fn workers(mut self, workers: usize) -> Self {
self.workers = Some(workers);
self
}
#[must_use = "builder method returns modified builder"]
pub fn tls(mut self, tls: TlsConfig) -> Self {
self.tls = Some(tls);
self
}
#[must_use = "builder method returns modified builder"]
pub fn limits(mut self, limits: ServerLimitsConfig) -> Self {
self.limits = Some(limits);
self
}
#[must_use = "building a config that is not used has no effect"]
pub fn build(self) -> HttpServerConfig {
HttpServerConfig {
port: self.port,
host: self.host,
workers: self.workers,
tls: self.tls,
limits: self.limits,
}
}
}
impl ServerLimitsConfig {
#[must_use = "builder does nothing until .build() is called"]
pub fn builder() -> ServerLimitsConfigBuilder {
ServerLimitsConfigBuilder::default()
}
}
#[derive(Debug)]
pub struct ServerLimitsConfigBuilder {
max_request_size: String,
request_timeout: String,
max_concurrent_requests: usize,
max_queue_depth: usize,
}
impl Default for ServerLimitsConfigBuilder {
fn default() -> Self {
Self {
max_request_size: default_max_request_size(),
request_timeout: default_request_timeout(),
max_concurrent_requests: default_max_concurrent(),
max_queue_depth: default_max_queue_depth(),
}
}
}
impl ServerLimitsConfigBuilder {
pub fn max_request_size(mut self, max_request_size: impl Into<String>) -> Self {
self.max_request_size = max_request_size.into();
self
}
pub fn request_timeout(mut self, request_timeout: impl Into<String>) -> Self {
self.request_timeout = request_timeout.into();
self
}
#[must_use = "builder method returns modified builder"]
pub const fn max_concurrent_requests(mut self, max_concurrent_requests: usize) -> Self {
self.max_concurrent_requests = max_concurrent_requests;
self
}
#[must_use = "builder method returns modified builder"]
pub const fn max_queue_depth(mut self, max_queue_depth: usize) -> Self {
self.max_queue_depth = max_queue_depth;
self
}
#[must_use = "building a config that is not used has no effect"]
pub fn build(self) -> ServerLimitsConfig {
ServerLimitsConfig {
max_request_size: self.max_request_size,
request_timeout: self.request_timeout,
max_concurrent_requests: self.max_concurrent_requests,
max_queue_depth: self.max_queue_depth,
}
}
}
impl DatabaseConfig {
#[must_use = "builder does nothing until .build() is called"]
pub fn builder() -> DatabaseConfigBuilder {
DatabaseConfigBuilder::default()
}
}
#[derive(Debug, Default)]
pub struct DatabaseConfigBuilder {
url_env: Option<String>,
pool_size: u32,
pool_timeout: Option<String>,
statement_timeout: Option<String>,
replicas: Vec<ReplicaConfig>,
health_check_interval: Option<String>,
}
impl DatabaseConfigBuilder {
pub fn url_env(mut self, url_env: impl Into<String>) -> Self {
self.url_env = Some(url_env.into());
self
}
#[must_use = "builder method returns modified builder"]
pub const fn pool_size(mut self, pool_size: u32) -> Self {
self.pool_size = pool_size;
self
}
pub fn pool_timeout(mut self, pool_timeout: impl Into<String>) -> Self {
self.pool_timeout = Some(pool_timeout.into());
self
}
pub fn statement_timeout(mut self, statement_timeout: impl Into<String>) -> Self {
self.statement_timeout = Some(statement_timeout.into());
self
}
#[must_use = "builder method returns modified builder"]
pub fn replica(mut self, replica: ReplicaConfig) -> Self {
self.replicas.push(replica);
self
}
pub fn health_check_interval(mut self, health_check_interval: impl Into<String>) -> Self {
self.health_check_interval = Some(health_check_interval.into());
self
}
pub fn build(self) -> Result<DatabaseConfig, String> {
let url_env =
self.url_env.ok_or_else(|| "DatabaseConfig: url_env is required".to_string())?;
Ok(DatabaseConfig {
url_env,
pool_size: if self.pool_size == 0 {
default_pool_size()
} else {
self.pool_size
},
pool_timeout: self.pool_timeout,
statement_timeout: self.statement_timeout,
replicas: self.replicas,
health_check_interval: self.health_check_interval,
})
}
}
impl LifecycleConfig {
#[must_use = "builder does nothing until .build() is called"]
pub fn builder() -> LifecycleConfigBuilder {
LifecycleConfigBuilder::default()
}
}
#[derive(Debug)]
pub struct LifecycleConfigBuilder {
shutdown_timeout: String,
shutdown_delay: String,
health_path: String,
ready_path: String,
}
impl Default for LifecycleConfigBuilder {
fn default() -> Self {
Self {
shutdown_timeout: default_shutdown_timeout(),
shutdown_delay: default_shutdown_delay(),
health_path: default_health_path(),
ready_path: default_ready_path(),
}
}
}
impl LifecycleConfigBuilder {
pub fn shutdown_timeout(mut self, shutdown_timeout: impl Into<String>) -> Self {
self.shutdown_timeout = shutdown_timeout.into();
self
}
pub fn shutdown_delay(mut self, shutdown_delay: impl Into<String>) -> Self {
self.shutdown_delay = shutdown_delay.into();
self
}
pub fn health_path(mut self, health_path: impl Into<String>) -> Self {
self.health_path = health_path.into();
self
}
pub fn ready_path(mut self, ready_path: impl Into<String>) -> Self {
self.ready_path = ready_path.into();
self
}
#[must_use = "building a config that is not used has no effect"]
pub fn build(self) -> LifecycleConfig {
LifecycleConfig {
shutdown_timeout: self.shutdown_timeout,
shutdown_delay: self.shutdown_delay,
health_path: self.health_path,
ready_path: self.ready_path,
}
}
}