use super::{ConfigError, ConfigFile, CorsConfig, DocsConfig, LoggingConfig, StaticConfig};
use std::{net::SocketAddr, time::Duration};
#[derive(Clone, Debug)]
pub struct Config {
pub bind_address: SocketAddr,
pub request_timeout: Duration,
pub shutdown_grace: Duration,
pub body_limit: usize,
pub max_in_flight: usize,
pub rate_limit_requests: u32,
pub rate_limit_window: Duration,
pub rate_limit_max_clients: usize,
pub cors: CorsConfig,
pub docs: DocsConfig,
pub static_files: StaticConfig,
pub logging: LoggingConfig,
pub versioning_enabled: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
bind_address: "127.0.0.1:3000".parse().unwrap(),
request_timeout: Duration::from_secs(30),
shutdown_grace: Duration::from_secs(10),
body_limit: 1_048_576,
max_in_flight: 256,
rate_limit_requests: 60,
rate_limit_window: Duration::from_secs(60),
rate_limit_max_clients: 10_000,
cors: CorsConfig::default(),
docs: DocsConfig::default(),
static_files: StaticConfig::default(),
logging: LoggingConfig::default(),
versioning_enabled: true,
}
}
}
impl Config {
pub fn from_file() -> Result<Self, ConfigError> {
Self::from_path("config/AppConfig.toml")
}
pub fn from_path(path: impl AsRef<std::path::Path>) -> Result<Self, ConfigError> {
Self::from_config(&ConfigFile::load(path)?)
}
pub(crate) fn from_config(file: &ConfigFile) -> Result<Self, ConfigError> {
file.check_keys(&[
"server.bind_address", "server.request_timeout_seconds", "server.shutdown_grace_seconds",
"server.body_limit_bytes", "server.max_in_flight_requests",
"rate_limit.requests", "rate_limit.window_seconds", "rate_limit.max_clients",
"cors.origins", "cors.allowed_methods", "cors.allowed_headers", "cors.exposed_headers",
"cors.allow_credentials", "cors.max_age_seconds", "docs.enabled",
"logging.filter", "logging.stdout_enabled", "logging.file_dir", "api.versioning_enabled",
])?;
let defaults = Self::default();
let number = |key, default, max| file.range(key, default, 1, max);
let bind_address = file.get_or("server.bind_address", defaults.bind_address.to_string())?
.parse().map_err(|_| ConfigError::invalid("server.bind_address", "must be an IP address and port"))?;
let config = Self {
bind_address,
request_timeout: Duration::from_secs(number("server.request_timeout_seconds", 30, 3600)?),
shutdown_grace: Duration::from_secs(number("server.shutdown_grace_seconds", 10, 300)?),
body_limit: number(
"server.body_limit_bytes",
defaults.body_limit as u64,
1_073_741_824,
)? as usize,
max_in_flight: number("server.max_in_flight_requests", 256, 100_000)? as usize,
rate_limit_requests: number("rate_limit.requests", 60, u32::MAX as u64)? as u32,
rate_limit_window: Duration::from_secs(number("rate_limit.window_seconds", 60, 86400)?),
rate_limit_max_clients: number("rate_limit.max_clients", 10_000, 1_000_000)? as usize,
cors: CorsConfig::from_config(file)?,
docs: DocsConfig::from_config(file)?,
static_files: StaticConfig::default(),
logging: LoggingConfig::from_config(file)?,
versioning_enabled: file.get_or("api.versioning_enabled", true)?,
};
Ok(config)
}
}