use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use zentinel_common::limits::Limits;
use crate::{AgentConfig, FilterConfig, ListenerConfig, RouteConfig, UpstreamConfig};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NamespaceConfig {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limits: Option<Limits>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub listeners: Vec<ListenerConfig>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub upstreams: HashMap<String, UpstreamConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub routes: Vec<RouteConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub agents: Vec<AgentConfig>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub filters: HashMap<String, FilterConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub services: Vec<ServiceConfig>,
#[serde(default, skip_serializing_if = "ExportConfig::is_empty")]
pub exports: ExportConfig,
}
impl NamespaceConfig {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
..Default::default()
}
}
pub fn is_empty(&self) -> bool {
self.listeners.is_empty()
&& self.upstreams.is_empty()
&& self.routes.is_empty()
&& self.agents.is_empty()
&& self.filters.is_empty()
&& self.services.is_empty()
&& self.limits.is_none()
}
pub fn get_service(&self, id: &str) -> Option<&ServiceConfig> {
self.services.iter().find(|s| s.id == id)
}
pub fn get_service_mut(&mut self, id: &str) -> Option<&mut ServiceConfig> {
self.services.iter_mut().find(|s| s.id == id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ServiceConfig {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub listener: Option<ListenerConfig>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub upstreams: HashMap<String, UpstreamConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub routes: Vec<RouteConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub agents: Vec<AgentConfig>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub filters: HashMap<String, FilterConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limits: Option<Limits>,
}
impl ServiceConfig {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
..Default::default()
}
}
pub fn is_empty(&self) -> bool {
self.listener.is_none()
&& self.upstreams.is_empty()
&& self.routes.is_empty()
&& self.agents.is_empty()
&& self.filters.is_empty()
&& self.limits.is_none()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExportConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub upstreams: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub agents: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub filters: Vec<String>,
}
impl ExportConfig {
pub fn is_empty(&self) -> bool {
self.upstreams.is_empty() && self.agents.is_empty() && self.filters.is_empty()
}
pub fn len(&self) -> usize {
self.upstreams.len() + self.agents.len() + self.filters.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ConnectionPoolConfig, HttpVersionConfig, UpstreamTarget, UpstreamTimeouts};
use zentinel_common::types::LoadBalancingAlgorithm;
fn test_upstream() -> UpstreamConfig {
UpstreamConfig {
id: "test-upstream".to_string(),
targets: vec![UpstreamTarget {
address: "127.0.0.1:8080".to_string(),
weight: 1,
max_requests: None,
metadata: HashMap::new(),
}],
load_balancing: LoadBalancingAlgorithm::RoundRobin,
sticky_session: None,
health_check: None,
connection_pool: ConnectionPoolConfig::default(),
timeouts: UpstreamTimeouts::default(),
tls: None,
http_version: HttpVersionConfig::default(),
}
}
#[test]
fn test_namespace_new() {
let ns = NamespaceConfig::new("api");
assert_eq!(ns.id, "api");
assert!(ns.is_empty());
}
#[test]
fn test_namespace_is_empty() {
let mut ns = NamespaceConfig::new("api");
assert!(ns.is_empty());
ns.upstreams.insert("backend".to_string(), test_upstream());
assert!(!ns.is_empty());
}
#[test]
fn test_service_new() {
let svc = ServiceConfig::new("payments");
assert_eq!(svc.id, "payments");
assert!(svc.is_empty());
}
#[test]
fn test_service_is_empty() {
let mut svc = ServiceConfig::new("payments");
assert!(svc.is_empty());
svc.upstreams.insert("backend".to_string(), test_upstream());
assert!(!svc.is_empty());
}
#[test]
fn test_export_config_is_empty() {
let exports = ExportConfig::default();
assert!(exports.is_empty());
assert_eq!(exports.len(), 0);
}
#[test]
fn test_export_config_len() {
let exports = ExportConfig {
upstreams: vec!["a".to_string(), "b".to_string()],
agents: vec!["c".to_string()],
filters: vec![],
};
assert!(!exports.is_empty());
assert_eq!(exports.len(), 3);
}
#[test]
fn test_namespace_get_service() {
let mut ns = NamespaceConfig::new("api");
ns.services.push(ServiceConfig::new("payments"));
ns.services.push(ServiceConfig::new("users"));
assert!(ns.get_service("payments").is_some());
assert!(ns.get_service("users").is_some());
assert!(ns.get_service("orders").is_none());
}
#[test]
fn test_namespace_serialization() {
let ns = NamespaceConfig {
id: "api".to_string(),
limits: None,
listeners: vec![],
upstreams: HashMap::new(),
routes: vec![],
agents: vec![],
filters: HashMap::new(),
services: vec![ServiceConfig::new("payments")],
exports: ExportConfig::default(),
};
let json = serde_json::to_string(&ns).unwrap();
let parsed: NamespaceConfig = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.id, "api");
assert_eq!(parsed.services.len(), 1);
assert_eq!(parsed.services[0].id, "payments");
}
}