use std::any::Any;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use dashmap::DashMap;
use crate::TypeUrl;
pub trait Resource: Send + Sync + fmt::Debug {
fn type_url(&self) -> &str;
fn name(&self) -> &str;
fn encode(&self) -> Result<prost_types::Any, Box<dyn std::error::Error + Send + Sync>>;
fn version(&self) -> Option<&str> {
None
}
fn as_any(&self) -> &dyn Any;
}
pub type BoxResource = Arc<dyn Resource>;
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct AnyResource {
type_url: String,
name: String,
version: Option<String>,
any: prost_types::Any,
}
#[allow(dead_code)] impl AnyResource {
#[must_use]
pub fn new(
type_url: impl Into<String>,
name: impl Into<String>,
any: prost_types::Any,
) -> Self {
Self {
type_url: type_url.into(),
name: name.into(),
version: None,
any,
}
}
#[must_use]
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
#[must_use]
pub fn inner(&self) -> &prost_types::Any {
&self.any
}
#[must_use]
pub fn into_inner(self) -> prost_types::Any {
self.any
}
}
impl Resource for AnyResource {
fn type_url(&self) -> &str {
&self.type_url
}
fn name(&self) -> &str {
&self.name
}
fn encode(&self) -> Result<prost_types::Any, Box<dyn std::error::Error + Send + Sync>> {
Ok(self.any.clone())
}
fn version(&self) -> Option<&str> {
self.version.as_deref()
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug, Default)]
pub struct ResourceRegistry {
types: HashMap<String, ResourceTypeInfo>,
}
#[derive(Debug, Clone)]
pub struct ResourceTypeInfo {
pub type_url: String,
pub short_name: String,
pub description: String,
}
impl ResourceRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_envoy_types() -> Self {
let mut registry = Self::new();
registry.register(ResourceTypeInfo {
type_url: TypeUrl::CLUSTER.to_string(),
short_name: "Cluster".to_string(),
description: "Cluster Discovery Service (CDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::ENDPOINT.to_string(),
short_name: "ClusterLoadAssignment".to_string(),
description: "Endpoint Discovery Service (EDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::LISTENER.to_string(),
short_name: "Listener".to_string(),
description: "Listener Discovery Service (LDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::ROUTE.to_string(),
short_name: "RouteConfiguration".to_string(),
description: "Route Discovery Service (RDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::SECRET.to_string(),
short_name: "Secret".to_string(),
description: "Secret Discovery Service (SDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::RUNTIME.to_string(),
short_name: "Runtime".to_string(),
description: "Runtime Discovery Service (RTDS)".to_string(),
});
registry
}
pub fn register(&mut self, info: ResourceTypeInfo) {
self.types.insert(info.type_url.clone(), info);
}
#[must_use]
pub fn get(&self, type_url: &str) -> Option<&ResourceTypeInfo> {
self.types.get(type_url)
}
#[must_use]
pub fn contains(&self, type_url: &str) -> bool {
self.types.contains_key(type_url)
}
#[must_use]
pub fn type_urls(&self) -> Vec<&str> {
self.types.keys().map(String::as_str).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.types.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.types.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &ResourceTypeInfo)> {
self.types.iter()
}
}
#[derive(Debug)]
pub struct SharedResourceRegistry {
types: DashMap<String, ResourceTypeInfo>,
}
impl Default for SharedResourceRegistry {
fn default() -> Self {
Self::new()
}
}
impl SharedResourceRegistry {
#[must_use]
pub fn new() -> Self {
Self {
types: DashMap::new(),
}
}
#[must_use]
pub fn with_envoy_types() -> Self {
let registry = Self::new();
registry.register(ResourceTypeInfo {
type_url: TypeUrl::CLUSTER.to_string(),
short_name: "Cluster".to_string(),
description: "Cluster Discovery Service (CDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::ENDPOINT.to_string(),
short_name: "ClusterLoadAssignment".to_string(),
description: "Endpoint Discovery Service (EDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::LISTENER.to_string(),
short_name: "Listener".to_string(),
description: "Listener Discovery Service (LDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::ROUTE.to_string(),
short_name: "RouteConfiguration".to_string(),
description: "Route Discovery Service (RDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::SECRET.to_string(),
short_name: "Secret".to_string(),
description: "Secret Discovery Service (SDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::RUNTIME.to_string(),
short_name: "Runtime".to_string(),
description: "Runtime Discovery Service (RTDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::SCOPED_ROUTE.to_string(),
short_name: "ScopedRouteConfiguration".to_string(),
description: "Scoped Route Discovery Service (SRDS)".to_string(),
});
registry.register(ResourceTypeInfo {
type_url: TypeUrl::VIRTUAL_HOST.to_string(),
short_name: "VirtualHost".to_string(),
description: "Virtual Host Discovery Service (VHDS)".to_string(),
});
registry
}
pub fn register(&self, info: ResourceTypeInfo) {
self.types.insert(info.type_url.clone(), info);
}
#[must_use]
pub fn get(&self, type_url: &str) -> Option<ResourceTypeInfo> {
self.types.get(type_url).map(|r| r.clone())
}
#[must_use]
pub fn contains(&self, type_url: &str) -> bool {
self.types.contains_key(type_url)
}
#[must_use]
pub fn type_urls(&self) -> Vec<String> {
self.types.iter().map(|r| r.key().clone()).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.types.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.types.is_empty()
}
pub fn validate(&self, type_url: &str) -> Result<(), String> {
if self.contains(type_url) {
Ok(())
} else {
let available: Vec<_> = self.type_urls();
Err(format!(
"Unknown resource type: {}. Available types: {:?}",
type_url, available
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_any_resource() {
let any = prost_types::Any {
type_url: TypeUrl::CLUSTER.to_string(),
value: vec![1, 2, 3],
};
let resource = AnyResource::new(TypeUrl::CLUSTER, "my-cluster", any);
assert_eq!(resource.type_url(), TypeUrl::CLUSTER);
assert_eq!(resource.name(), "my-cluster");
assert!(resource.version().is_none());
}
#[test]
fn test_any_resource_with_version() {
let any = prost_types::Any {
type_url: TypeUrl::CLUSTER.to_string(),
value: vec![],
};
let resource = AnyResource::new(TypeUrl::CLUSTER, "my-cluster", any).with_version("v1");
assert_eq!(resource.version(), Some("v1"));
}
#[test]
fn test_registry_new() {
let registry = ResourceRegistry::new();
assert!(registry.is_empty());
}
#[test]
fn test_registry_with_envoy_types() {
let registry = ResourceRegistry::with_envoy_types();
assert!(!registry.is_empty());
assert!(registry.contains(TypeUrl::CLUSTER));
assert!(registry.contains(TypeUrl::ENDPOINT));
}
#[test]
fn test_registry_register() {
let mut registry = ResourceRegistry::new();
registry.register(ResourceTypeInfo {
type_url: "custom.type".to_string(),
short_name: "Custom".to_string(),
description: "Custom type".to_string(),
});
assert!(registry.contains("custom.type"));
assert_eq!(registry.len(), 1);
}
#[test]
fn test_registry_get() {
let registry = ResourceRegistry::with_envoy_types();
let info = registry.get(TypeUrl::CLUSTER);
assert!(info.is_some(), "CLUSTER type should be registered");
#[allow(clippy::unwrap_used)]
let info = info.unwrap();
assert_eq!(info.short_name, "Cluster");
}
#[test]
fn test_shared_registry_new() {
let registry = SharedResourceRegistry::new();
assert!(registry.is_empty());
}
#[test]
fn test_shared_registry_with_envoy_types() {
let registry = SharedResourceRegistry::with_envoy_types();
assert!(!registry.is_empty());
assert!(registry.contains(TypeUrl::CLUSTER));
assert!(registry.contains(TypeUrl::ENDPOINT));
assert!(registry.contains(TypeUrl::LISTENER));
assert!(registry.contains(TypeUrl::ROUTE));
assert!(registry.contains(TypeUrl::SECRET));
assert!(registry.contains(TypeUrl::RUNTIME));
assert!(registry.contains(TypeUrl::SCOPED_ROUTE));
assert!(registry.contains(TypeUrl::VIRTUAL_HOST));
assert_eq!(registry.len(), 8);
}
#[test]
fn test_shared_registry_register() {
let registry = SharedResourceRegistry::new();
registry.register(ResourceTypeInfo {
type_url: "custom.type".to_string(),
short_name: "Custom".to_string(),
description: "Custom type".to_string(),
});
assert!(registry.contains("custom.type"));
assert_eq!(registry.len(), 1);
}
#[test]
fn test_shared_registry_get() {
let registry = SharedResourceRegistry::with_envoy_types();
let info = registry.get(TypeUrl::CLUSTER);
assert!(info.is_some(), "CLUSTER type should be registered");
#[allow(clippy::unwrap_used)]
let info = info.unwrap();
assert_eq!(info.short_name, "Cluster");
}
#[test]
fn test_shared_registry_validate() {
let registry = SharedResourceRegistry::with_envoy_types();
assert!(registry.validate(TypeUrl::CLUSTER).is_ok());
let err = registry.validate("unknown.type").unwrap_err();
assert!(err.contains("Unknown resource type"));
assert!(err.contains("unknown.type"));
}
#[test]
fn test_shared_registry_thread_safety() {
use std::sync::Arc;
use std::thread;
let registry = Arc::new(SharedResourceRegistry::new());
let mut handles = vec![];
for i in 0..10 {
let reg = Arc::clone(®istry);
handles.push(thread::spawn(move || {
reg.register(ResourceTypeInfo {
type_url: format!("type.{}", i),
short_name: format!("Type{}", i),
description: format!("Type {} description", i),
});
}));
}
for handle in handles {
handle.join().expect("Thread panicked");
}
assert_eq!(registry.len(), 10);
}
}