use serde::{Deserialize, Serialize};
use std::env;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum StorageConfig {
#[default]
InMemory,
Sqlx {
url: String,
#[serde(default = "default_max_connections")]
max_connections: u32,
#[serde(default)]
enable_logging: bool,
},
}
impl StorageConfig {
pub fn from_env() -> Self {
if let Ok(url) = env::var("DATABASE_URL") {
Self::Sqlx {
url,
max_connections: env::var("DATABASE_MAX_CONNECTIONS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or_else(default_max_connections),
enable_logging: env::var("DATABASE_ENABLE_LOGGING")
.ok()
.map(|s| s.to_lowercase() == "true" || s == "1")
.unwrap_or(false),
}
} else {
Self::InMemory
}
}
}
fn default_max_connections() -> u32 {
10
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
#[serde(default = "default_host")]
pub host: String,
#[serde(default = "default_http_port")]
pub http_port: u16,
#[serde(default)]
pub storage: StorageConfig,
#[serde(default)]
pub auth: AuthConfig,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: default_host(),
http_port: default_http_port(),
storage: StorageConfig::default(),
auth: AuthConfig::default(),
}
}
}
impl ServerConfig {
pub fn from_env() -> Self {
Self {
host: env::var("HOST").unwrap_or_else(|_| default_host()),
http_port: env::var("HTTP_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or_else(default_http_port),
storage: StorageConfig::from_env(),
auth: AuthConfig::from_env(),
}
}
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
if let Ok(config_path) = env::var("CONFIG_FILE") {
let config_str = std::fs::read_to_string(config_path)?;
let config: Self = serde_json::from_str(&config_str)?;
Ok(config)
} else {
Ok(Self::from_env())
}
}
}
fn default_host() -> String {
"127.0.0.1".to_string()
}
fn default_http_port() -> u16 {
8080
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum AuthConfig {
#[default]
None,
BearerToken {
tokens: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
format: Option<String>,
},
ApiKey {
keys: Vec<String>,
#[serde(default = "default_api_key_location")]
location: String,
#[serde(default = "default_api_key_name")]
name: String,
},
}
impl AuthConfig {
pub fn from_env() -> Self {
if let Ok(tokens_str) = env::var("AUTH_BEARER_TOKENS") {
let tokens: Vec<String> = tokens_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if !tokens.is_empty() {
return Self::BearerToken {
tokens,
format: env::var("AUTH_BEARER_FORMAT").ok(),
};
}
}
if let Ok(keys_str) = env::var("AUTH_API_KEYS") {
let keys: Vec<String> = keys_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if !keys.is_empty() {
return Self::ApiKey {
keys,
location: env::var("AUTH_API_KEY_LOCATION")
.unwrap_or_else(|_| default_api_key_location()),
name: env::var("AUTH_API_KEY_NAME").unwrap_or_else(|_| default_api_key_name()),
};
}
}
Self::None
}
}
fn default_api_key_location() -> String {
"header".to_string()
}
fn default_api_key_name() -> String {
"X-API-Key".to_string()
}