pub const DEFAULT_CONFIG_KDL: &str = r#"
// Zentinel Default Configuration
// This minimal config is used when no configuration file is provided.
// For production use, create a configuration file with your routes and upstreams.
// Configuration schema version - used for compatibility checking
schema-version "1.0"
server {
worker-threads 0 // Auto-detect CPU cores
max-connections 10000
graceful-shutdown-timeout-secs 30
}
listeners {
// Main HTTP listener
listener "default-http" {
address "0.0.0.0:8080"
protocol "http"
request-timeout-secs 60
keepalive-timeout-secs 75
default-route "status"
}
// Admin/health listener on separate port
listener "admin" {
address "0.0.0.0:9090"
protocol "http"
request-timeout-secs 5
keepalive-timeout-secs 30
default-route "health"
}
}
routes {
// JSON status page - catch-all for main listener
route "status" {
priority "low"
matches {
path-prefix "/"
}
service-type "builtin"
builtin-handler "status"
}
// Health check endpoint on admin port
route "health" {
priority "high"
matches {
path "/health"
path "/healthz"
path "/ready"
}
service-type "builtin"
builtin-handler "health"
}
// Metrics endpoint on admin port
route "metrics" {
priority "high"
matches {
path "/metrics"
}
service-type "builtin"
builtin-handler "metrics"
}
// Config dump endpoint on admin port
route "config" {
priority "high"
matches {
path "/admin/config"
path "/config"
}
service-type "builtin"
builtin-handler "config"
}
// Upstream health status endpoint on admin port
route "upstreams" {
priority "high"
matches {
path "/admin/upstreams"
path "/upstreams"
}
service-type "builtin"
builtin-handler "upstreams"
}
// Cache statistics endpoint on admin port
route "cache-stats" {
priority "high"
matches {
path "/admin/cache/stats"
path "/cache/stats"
}
service-type "builtin"
builtin-handler "cache-stats"
}
// Cache purge endpoint on admin port (PURGE method or POST)
route "cache-purge" {
priority "high"
matches {
path-prefix "/admin/cache/purge"
path-prefix "/cache/purge"
}
service-type "builtin"
builtin-handler "cache-purge"
}
}
limits {
max-header-size-bytes 8192
max-header-count 100
max-body-size-bytes 1048576 // 1MB default
max-connections-per-client 100
}
// Observability uses defaults:
// - Metrics enabled at /metrics
// - JSON logging at info level
// - Error log enabled at /var/log/zentinel/error.log (level: warn)
observability {
logging {
error-log {
enabled #true
file "/var/log/zentinel/error.log"
level "warn"
}
}
}
"#;
use crate::{
BuiltinHandler, Config, GlobalRateLimitConfig, ListenerConfig, ListenerProtocol,
MatchCondition, ObservabilityConfig, RouteConfig, RoutePolicies, ServerConfig, ServiceType,
};
use std::collections::HashMap;
use zentinel_common::{limits::Limits, types::Priority};
pub fn create_default_config() -> Config {
Config {
schema_version: crate::CURRENT_SCHEMA_VERSION.to_string(),
server: ServerConfig {
worker_threads: 0, max_connections: 10000,
graceful_shutdown_timeout_secs: 30,
daemon: false,
pid_file: None,
user: None,
group: None,
working_directory: None,
trace_id_format: Default::default(),
auto_reload: false,
},
listeners: vec![
ListenerConfig {
id: "default-http".to_string(),
address: "0.0.0.0:8080".to_string(),
protocol: ListenerProtocol::Http,
tls: None,
default_route: Some("status".to_string()),
request_timeout_secs: 60,
keepalive_timeout_secs: 75,
max_concurrent_streams: 100,
keepalive_max_requests: None,
},
ListenerConfig {
id: "admin".to_string(),
address: "0.0.0.0:9090".to_string(),
protocol: ListenerProtocol::Http,
tls: None,
default_route: Some("health".to_string()),
request_timeout_secs: 5,
keepalive_timeout_secs: 30,
max_concurrent_streams: 100,
keepalive_max_requests: None,
},
],
routes: vec![
RouteConfig {
id: "status".to_string(),
priority: Priority::LOW,
matches: vec![MatchCondition::PathPrefix("/".to_string())],
upstream: None,
service_type: ServiceType::Builtin,
policies: RoutePolicies::default(),
filters: vec![],
builtin_handler: Some(BuiltinHandler::Status),
waf_enabled: false,
circuit_breaker: None,
retry_policy: None,
static_files: None,
api_schema: None,
inference: None,
error_pages: None,
websocket: false,
websocket_inspection: false,
shadow: None,
fallback: None,
},
RouteConfig {
id: "health".to_string(),
priority: Priority::HIGH,
matches: vec![
MatchCondition::Path("/health".to_string()),
MatchCondition::Path("/healthz".to_string()),
MatchCondition::Path("/ready".to_string()),
],
upstream: None,
service_type: ServiceType::Builtin,
policies: RoutePolicies::default(),
filters: vec![],
builtin_handler: Some(BuiltinHandler::Health),
waf_enabled: false,
circuit_breaker: None,
retry_policy: None,
static_files: None,
api_schema: None,
inference: None,
error_pages: None,
websocket: false,
websocket_inspection: false,
shadow: None,
fallback: None,
},
RouteConfig {
id: "metrics".to_string(),
priority: Priority::HIGH,
matches: vec![MatchCondition::Path("/metrics".to_string())],
upstream: None,
service_type: ServiceType::Builtin,
policies: RoutePolicies::default(),
filters: vec![],
builtin_handler: Some(BuiltinHandler::Metrics),
waf_enabled: false,
circuit_breaker: None,
retry_policy: None,
static_files: None,
api_schema: None,
inference: None,
error_pages: None,
websocket: false,
websocket_inspection: false,
shadow: None,
fallback: None,
},
RouteConfig {
id: "config".to_string(),
priority: Priority::HIGH,
matches: vec![
MatchCondition::Path("/admin/config".to_string()),
MatchCondition::Path("/config".to_string()),
],
upstream: None,
service_type: ServiceType::Builtin,
policies: RoutePolicies::default(),
filters: vec![],
builtin_handler: Some(BuiltinHandler::Config),
waf_enabled: false,
circuit_breaker: None,
retry_policy: None,
static_files: None,
api_schema: None,
inference: None,
error_pages: None,
websocket: false,
websocket_inspection: false,
shadow: None,
fallback: None,
},
RouteConfig {
id: "upstreams".to_string(),
priority: Priority::HIGH,
matches: vec![
MatchCondition::Path("/admin/upstreams".to_string()),
MatchCondition::Path("/upstreams".to_string()),
],
upstream: None,
service_type: ServiceType::Builtin,
policies: RoutePolicies::default(),
filters: vec![],
builtin_handler: Some(BuiltinHandler::Upstreams),
waf_enabled: false,
circuit_breaker: None,
retry_policy: None,
static_files: None,
api_schema: None,
inference: None,
error_pages: None,
websocket: false,
websocket_inspection: false,
shadow: None,
fallback: None,
},
RouteConfig {
id: "cache-stats".to_string(),
priority: Priority::HIGH,
matches: vec![
MatchCondition::Path("/admin/cache/stats".to_string()),
MatchCondition::Path("/cache/stats".to_string()),
],
upstream: None,
service_type: ServiceType::Builtin,
policies: RoutePolicies::default(),
filters: vec![],
builtin_handler: Some(BuiltinHandler::CacheStats),
waf_enabled: false,
circuit_breaker: None,
retry_policy: None,
static_files: None,
api_schema: None,
inference: None,
error_pages: None,
websocket: false,
websocket_inspection: false,
shadow: None,
fallback: None,
},
RouteConfig {
id: "cache-purge".to_string(),
priority: Priority::HIGH,
matches: vec![
MatchCondition::PathPrefix("/admin/cache/purge".to_string()),
MatchCondition::PathPrefix("/cache/purge".to_string()),
],
upstream: None,
service_type: ServiceType::Builtin,
policies: RoutePolicies::default(),
filters: vec![],
builtin_handler: Some(BuiltinHandler::CachePurge),
waf_enabled: false,
circuit_breaker: None,
retry_policy: None,
static_files: None,
api_schema: None,
inference: None,
error_pages: None,
websocket: false,
websocket_inspection: false,
shadow: None,
fallback: None,
},
],
upstreams: HashMap::new(),
filters: HashMap::new(),
agents: vec![],
waf: None,
namespaces: vec![],
limits: Limits::default(),
observability: ObservabilityConfig::default(),
rate_limits: GlobalRateLimitConfig::default(),
cache: None,
default_upstream: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Config;
#[test]
fn test_default_config_kdl_parses() {
let config = Config::from_kdl(DEFAULT_CONFIG_KDL);
assert!(
config.is_ok(),
"Default KDL config should parse: {:?}",
config.err()
);
}
#[test]
fn test_create_default_config() {
let config = create_default_config();
assert_eq!(config.listeners.len(), 2);
assert_eq!(config.routes.len(), 7);
assert!(config.routes.iter().any(|r| r.id == "status"));
assert!(config.routes.iter().any(|r| r.id == "health"));
assert!(config.routes.iter().any(|r| r.id == "config"));
assert!(config.routes.iter().any(|r| r.id == "upstreams"));
assert!(config.routes.iter().any(|r| r.id == "cache-stats"));
assert!(config.routes.iter().any(|r| r.id == "cache-purge"));
}
}