use crate::errors::{KodeBridgeError, Result};
use crate::pool::PoolConfig;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct GlobalConfig {
pub client: ClientGlobalConfig,
pub streaming: StreamingGlobalConfig,
pub logging: LoggingConfig,
pub features: FeatureFlags,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientGlobalConfig {
pub default_timeout_ms: u64,
pub enable_pooling: bool,
pub pool: PoolConfig,
pub max_retries: usize,
pub retry_delay_ms: u64,
pub connection_timeout_ms: u64,
}
impl Default for ClientGlobalConfig {
fn default() -> Self {
Self {
default_timeout_ms: 10_000, enable_pooling: true,
pool: PoolConfig::default(),
max_retries: 5, retry_delay_ms: 50, connection_timeout_ms: 5_000, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamingGlobalConfig {
pub default_timeout_ms: u64,
pub buffer_size: usize,
pub max_retries: usize,
pub retry_delay_ms: u64,
}
impl Default for StreamingGlobalConfig {
fn default() -> Self {
Self {
default_timeout_ms: 60_000,
buffer_size: 8192,
max_retries: 3,
retry_delay_ms: 100,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub enabled: bool,
pub level: String,
pub structured: bool,
pub log_requests: bool,
pub log_connections: bool,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
enabled: false,
level: "info".to_string(),
structured: false,
log_requests: false,
log_connections: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureFlags {
pub http2_support: bool,
pub compression: bool,
pub caching: bool,
pub metrics: bool,
pub keep_alive: bool,
pub auto_reconnect: bool,
}
impl Default for FeatureFlags {
fn default() -> Self {
Self {
http2_support: false,
compression: false,
caching: false,
metrics: false,
keep_alive: true,
auto_reconnect: true,
}
}
}
impl GlobalConfig {
pub fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to read config file: {}", e)))?;
toml::from_str(&content)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to parse TOML config: {}", e)))
}
pub fn from_json_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to read config file: {}", e)))?;
serde_json::from_str(&content)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to parse JSON config: {}", e)))
}
pub fn save_toml_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let content = toml::to_string_pretty(self)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to serialize config: {}", e)))?;
std::fs::write(path, content)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to write config file: {}", e)))
}
pub fn save_json_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let content = serde_json::to_string_pretty(self)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to serialize config: {}", e)))?;
std::fs::write(path, content)
.map_err(|e| KodeBridgeError::configuration(format!("Failed to write config file: {}", e)))
}
pub fn apply_env_overrides(&mut self) {
if let Ok(timeout) = std::env::var("KODE_BRIDGE_TIMEOUT_MS") {
if let Ok(timeout_ms) = timeout.parse::<u64>() {
self.client.default_timeout_ms = timeout_ms;
}
}
if let Ok(pooling) = std::env::var("KODE_BRIDGE_ENABLE_POOLING") {
self.client.enable_pooling = pooling.to_lowercase() == "true";
}
if let Ok(retries) = std::env::var("KODE_BRIDGE_MAX_RETRIES") {
if let Ok(retries_num) = retries.parse::<usize>() {
self.client.max_retries = retries_num;
}
}
if let Ok(log_level) = std::env::var("KODE_BRIDGE_LOG_LEVEL") {
self.logging.level = log_level;
self.logging.enabled = true;
}
if let Ok(pool_size) = std::env::var("KODE_BRIDGE_POOL_SIZE") {
if let Ok(size) = pool_size.parse::<usize>() {
self.client.pool.max_size = size;
}
}
}
pub const fn client_timeout(&self) -> Duration {
Duration::from_millis(self.client.default_timeout_ms)
}
pub const fn streaming_timeout(&self) -> Duration {
Duration::from_millis(self.streaming.default_timeout_ms)
}
pub const fn retry_delay(&self) -> Duration {
Duration::from_millis(self.client.retry_delay_ms)
}
pub const fn connection_timeout(&self) -> Duration {
Duration::from_millis(self.client.connection_timeout_ms)
}
pub fn is_feature_enabled(&self, feature: &str) -> bool {
match feature {
"http2" => self.features.http2_support,
"compression" => self.features.compression,
"caching" => self.features.caching,
"metrics" => self.features.metrics,
"keep_alive" => self.features.keep_alive,
"auto_reconnect" => self.features.auto_reconnect,
_ => false,
}
}
pub fn validate(&self) -> Result<()> {
if self.client.default_timeout_ms == 0 {
return Err(KodeBridgeError::configuration("Client timeout cannot be zero"));
}
if self.client.max_retries > 10 {
return Err(KodeBridgeError::configuration("Max retries should be <= 10"));
}
if self.client.pool.max_size == 0 {
return Err(KodeBridgeError::configuration("Pool max size cannot be zero"));
}
if self.client.pool.min_idle > self.client.pool.max_size {
return Err(KodeBridgeError::configuration(
"Pool min_idle cannot be greater than max_size",
));
}
if !["trace", "debug", "info", "warn", "error"].contains(&self.logging.level.as_str()) {
return Err(KodeBridgeError::configuration("Invalid log level"));
}
Ok(())
}
}
pub struct ConfigBuilder {
config: GlobalConfig,
}
impl ConfigBuilder {
pub fn new() -> Self {
Self {
config: GlobalConfig::default(),
}
}
pub const fn client_timeout(mut self, timeout: Duration) -> Self {
self.config.client.default_timeout_ms = timeout.as_millis() as u64;
self
}
pub const fn enable_pooling(mut self, enabled: bool) -> Self {
self.config.client.enable_pooling = enabled;
self
}
pub const fn pool_config(mut self, pool_config: PoolConfig) -> Self {
self.config.client.pool = pool_config;
self
}
pub const fn max_retries(mut self, retries: usize) -> Self {
self.config.client.max_retries = retries;
self
}
pub fn enable_logging(mut self, level: &str) -> Self {
self.config.logging.enabled = true;
self.config.logging.level = level.to_string();
self
}
pub fn enable_feature(mut self, feature: &str) -> Self {
match feature {
"compression" => self.config.features.compression = true,
"caching" => self.config.features.caching = true,
"metrics" => self.config.features.metrics = true,
"keep_alive" => self.config.features.keep_alive = true,
"auto_reconnect" => self.config.features.auto_reconnect = true,
_ => {}
}
self
}
pub fn build(self) -> Result<GlobalConfig> {
self.config.validate()?;
Ok(self.config)
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self::new()
}
}