use super::actions::{CacheBackend, MetricsBackend};
use ipnet::IpNet;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GlobalConfig {
pub storage: StorageType,
pub cache: CacheBackend,
pub metrics: MetricsBackend,
#[serde(default)]
pub trusted_proxies: TrustedProxyConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StorageType {
#[default]
Memory,
PostgreSQL,
Redis,
}
impl StorageType {
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"memory" => Some(Self::Memory),
"postgresql" | "postgres" => Some(Self::PostgreSQL),
"redis" => Some(Self::Redis),
_ => None,
}
}
}
impl From<&str> for StorageType {
fn from(s: &str) -> Self {
Self::parse(s).unwrap_or_default()
}
}
impl std::fmt::Display for StorageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Memory => write!(f, "memory"),
Self::PostgreSQL => write!(f, "postgresql"),
Self::Redis => write!(f, "redis"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TrustedProxyConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub proxies: Vec<String>,
#[serde(default = "default_max_hops")]
pub max_hops: usize,
}
fn default_max_hops() -> usize {
10
}
impl Default for GlobalConfig {
fn default() -> Self {
Self {
storage: StorageType::Memory,
cache: CacheBackend::default(),
metrics: MetricsBackend::default(),
trusted_proxies: TrustedProxyConfig::default(),
}
}
}
impl Default for TrustedProxyConfig {
fn default() -> Self {
Self {
enabled: false,
proxies: Vec::new(),
max_hops: default_max_hops(),
}
}
}
impl TrustedProxyConfig {
pub fn validate(&self) -> Result<(), String> {
if !self.enabled {
return Ok(());
}
for proxy in &self.proxies {
if let Err(e) = Self::parse_cidr_or_ip(proxy) {
return Err(format!("无效的代理地址 '{}': {}", proxy, e));
}
}
Ok(())
}
fn parse_cidr_or_ip(s: &str) -> Result<(), String> {
if s.contains('/') {
s.parse::<IpNet>().map(|_| ()).map_err(|e| e.to_string())
} else {
s.parse::<std::net::IpAddr>()
.map(|_| ())
.map_err(|e| e.to_string())
}
}
pub fn is_trusted(&self, ip: &str) -> bool {
if !self.enabled || self.proxies.is_empty() {
return false;
}
let Ok(ip_addr) = ip.parse::<std::net::IpAddr>() else {
return false;
};
for proxy in &self.proxies {
if proxy.contains('/') {
if let Ok(network) = proxy.parse::<IpNet>() {
if network.contains(&ip_addr) {
return true;
}
}
} else if proxy == ip {
return true;
}
}
false
}
}
impl GlobalConfig {
pub fn validate(&self) -> Result<(), String> {
self.trusted_proxies.validate()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_storage_type_parse() {
assert_eq!(StorageType::parse("memory"), Some(StorageType::Memory));
assert_eq!(
StorageType::parse("postgresql"),
Some(StorageType::PostgreSQL)
);
assert_eq!(
StorageType::parse("postgres"),
Some(StorageType::PostgreSQL)
);
assert_eq!(StorageType::parse("redis"), Some(StorageType::Redis));
assert_eq!(StorageType::parse("invalid"), None);
}
#[test]
fn test_storage_type_from_str() {
assert_eq!(StorageType::from("memory"), StorageType::Memory);
assert_eq!(StorageType::from("invalid"), StorageType::Memory);
}
#[test]
fn test_storage_type_display() {
assert_eq!(format!("{}", StorageType::Memory), "memory");
assert_eq!(format!("{}", StorageType::PostgreSQL), "postgresql");
assert_eq!(format!("{}", StorageType::Redis), "redis");
}
#[test]
fn test_storage_type_default() {
assert_eq!(StorageType::default(), StorageType::Memory);
}
#[test]
fn test_trusted_proxy_config_default() {
let config = TrustedProxyConfig::default();
assert!(!config.enabled);
assert!(config.proxies.is_empty());
assert_eq!(config.max_hops, 10);
}
#[test]
fn test_trusted_proxy_config_validate_disabled() {
let config = TrustedProxyConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_trusted_proxy_config_validate_valid() {
let config = TrustedProxyConfig {
enabled: true,
proxies: vec!["192.168.1.1".to_string(), "10.0.0.0/8".to_string()],
max_hops: 10,
};
assert!(config.validate().is_ok());
}
#[test]
fn test_trusted_proxy_config_validate_invalid() {
let config = TrustedProxyConfig {
enabled: true,
proxies: vec!["not-an-ip".to_string()],
max_hops: 10,
};
assert!(config.validate().is_err());
}
#[test]
fn test_trusted_proxy_is_trusted() {
let config = TrustedProxyConfig {
enabled: true,
proxies: vec!["192.168.1.1".to_string(), "10.0.0.0/8".to_string()],
max_hops: 10,
};
assert!(config.is_trusted("192.168.1.1"));
assert!(config.is_trusted("10.0.0.1"));
assert!(!config.is_trusted("1.2.3.4"));
assert!(!config.is_trusted("not-an-ip"));
}
#[test]
fn test_trusted_proxy_is_trusted_disabled() {
let config = TrustedProxyConfig::default();
assert!(!config.is_trusted("192.168.1.1"));
}
#[test]
fn test_global_config_default() {
let config = GlobalConfig::default();
assert_eq!(config.storage, StorageType::Memory);
assert_eq!(config.cache, CacheBackend::default());
assert_eq!(config.metrics, MetricsBackend::default());
assert!(!config.trusted_proxies.enabled);
}
#[test]
fn test_global_config_validate() {
let config = GlobalConfig::default();
assert!(config.validate().is_ok());
}
}