1use std::any::Any;
7use std::collections::HashMap;
8use std::fmt;
9use std::sync::Arc;
10
11use dashmap::DashMap;
12
13use crate::TypeUrl;
14
15pub trait Resource: Send + Sync + fmt::Debug {
56 fn type_url(&self) -> &str;
58
59 fn name(&self) -> &str;
61
62 fn encode(&self) -> Result<prost_types::Any, Box<dyn std::error::Error + Send + Sync>>;
64
65 fn version(&self) -> Option<&str> {
67 None
68 }
69
70 fn as_any(&self) -> &dyn Any;
72}
73
74pub type BoxResource = Arc<dyn Resource>;
77
78#[derive(Debug, Clone)]
83#[allow(dead_code)] pub struct AnyResource {
85 type_url: String,
86 name: String,
87 version: Option<String>,
88 any: prost_types::Any,
89}
90
91#[allow(dead_code)] impl AnyResource {
93 #[must_use]
95 pub fn new(
96 type_url: impl Into<String>,
97 name: impl Into<String>,
98 any: prost_types::Any,
99 ) -> Self {
100 Self {
101 type_url: type_url.into(),
102 name: name.into(),
103 version: None,
104 any,
105 }
106 }
107
108 #[must_use]
110 pub fn with_version(mut self, version: impl Into<String>) -> Self {
111 self.version = Some(version.into());
112 self
113 }
114
115 #[must_use]
117 pub fn inner(&self) -> &prost_types::Any {
118 &self.any
119 }
120
121 #[must_use]
123 pub fn into_inner(self) -> prost_types::Any {
124 self.any
125 }
126}
127
128impl Resource for AnyResource {
129 fn type_url(&self) -> &str {
130 &self.type_url
131 }
132
133 fn name(&self) -> &str {
134 &self.name
135 }
136
137 fn encode(&self) -> Result<prost_types::Any, Box<dyn std::error::Error + Send + Sync>> {
138 Ok(self.any.clone())
139 }
140
141 fn version(&self) -> Option<&str> {
142 self.version.as_deref()
143 }
144
145 fn as_any(&self) -> &dyn Any {
146 self
147 }
148}
149
150#[derive(Debug, Default)]
155pub struct ResourceRegistry {
156 types: HashMap<String, ResourceTypeInfo>,
157}
158
159#[derive(Debug, Clone)]
161pub struct ResourceTypeInfo {
162 pub type_url: String,
164 pub short_name: String,
166 pub description: String,
168}
169
170impl ResourceRegistry {
171 #[must_use]
173 pub fn new() -> Self {
174 Self::default()
175 }
176
177 #[must_use]
179 pub fn with_envoy_types() -> Self {
180 let mut registry = Self::new();
181
182 registry.register(ResourceTypeInfo {
183 type_url: TypeUrl::CLUSTER.to_string(),
184 short_name: "Cluster".to_string(),
185 description: "Cluster Discovery Service (CDS)".to_string(),
186 });
187
188 registry.register(ResourceTypeInfo {
189 type_url: TypeUrl::ENDPOINT.to_string(),
190 short_name: "ClusterLoadAssignment".to_string(),
191 description: "Endpoint Discovery Service (EDS)".to_string(),
192 });
193
194 registry.register(ResourceTypeInfo {
195 type_url: TypeUrl::LISTENER.to_string(),
196 short_name: "Listener".to_string(),
197 description: "Listener Discovery Service (LDS)".to_string(),
198 });
199
200 registry.register(ResourceTypeInfo {
201 type_url: TypeUrl::ROUTE.to_string(),
202 short_name: "RouteConfiguration".to_string(),
203 description: "Route Discovery Service (RDS)".to_string(),
204 });
205
206 registry.register(ResourceTypeInfo {
207 type_url: TypeUrl::SECRET.to_string(),
208 short_name: "Secret".to_string(),
209 description: "Secret Discovery Service (SDS)".to_string(),
210 });
211
212 registry.register(ResourceTypeInfo {
213 type_url: TypeUrl::RUNTIME.to_string(),
214 short_name: "Runtime".to_string(),
215 description: "Runtime Discovery Service (RTDS)".to_string(),
216 });
217
218 registry
219 }
220
221 pub fn register(&mut self, info: ResourceTypeInfo) {
223 self.types.insert(info.type_url.clone(), info);
224 }
225
226 #[must_use]
228 pub fn get(&self, type_url: &str) -> Option<&ResourceTypeInfo> {
229 self.types.get(type_url)
230 }
231
232 #[must_use]
234 pub fn contains(&self, type_url: &str) -> bool {
235 self.types.contains_key(type_url)
236 }
237
238 #[must_use]
240 pub fn type_urls(&self) -> Vec<&str> {
241 self.types.keys().map(String::as_str).collect()
242 }
243
244 #[must_use]
246 pub fn len(&self) -> usize {
247 self.types.len()
248 }
249
250 #[must_use]
252 pub fn is_empty(&self) -> bool {
253 self.types.is_empty()
254 }
255
256 pub fn iter(&self) -> impl Iterator<Item = (&String, &ResourceTypeInfo)> {
258 self.types.iter()
259 }
260}
261
262#[derive(Debug)]
282pub struct SharedResourceRegistry {
283 types: DashMap<String, ResourceTypeInfo>,
284}
285
286impl Default for SharedResourceRegistry {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292impl SharedResourceRegistry {
293 #[must_use]
295 pub fn new() -> Self {
296 Self {
297 types: DashMap::new(),
298 }
299 }
300
301 #[must_use]
303 pub fn with_envoy_types() -> Self {
304 let registry = Self::new();
305
306 registry.register(ResourceTypeInfo {
307 type_url: TypeUrl::CLUSTER.to_string(),
308 short_name: "Cluster".to_string(),
309 description: "Cluster Discovery Service (CDS)".to_string(),
310 });
311
312 registry.register(ResourceTypeInfo {
313 type_url: TypeUrl::ENDPOINT.to_string(),
314 short_name: "ClusterLoadAssignment".to_string(),
315 description: "Endpoint Discovery Service (EDS)".to_string(),
316 });
317
318 registry.register(ResourceTypeInfo {
319 type_url: TypeUrl::LISTENER.to_string(),
320 short_name: "Listener".to_string(),
321 description: "Listener Discovery Service (LDS)".to_string(),
322 });
323
324 registry.register(ResourceTypeInfo {
325 type_url: TypeUrl::ROUTE.to_string(),
326 short_name: "RouteConfiguration".to_string(),
327 description: "Route Discovery Service (RDS)".to_string(),
328 });
329
330 registry.register(ResourceTypeInfo {
331 type_url: TypeUrl::SECRET.to_string(),
332 short_name: "Secret".to_string(),
333 description: "Secret Discovery Service (SDS)".to_string(),
334 });
335
336 registry.register(ResourceTypeInfo {
337 type_url: TypeUrl::RUNTIME.to_string(),
338 short_name: "Runtime".to_string(),
339 description: "Runtime Discovery Service (RTDS)".to_string(),
340 });
341
342 registry.register(ResourceTypeInfo {
343 type_url: TypeUrl::SCOPED_ROUTE.to_string(),
344 short_name: "ScopedRouteConfiguration".to_string(),
345 description: "Scoped Route Discovery Service (SRDS)".to_string(),
346 });
347
348 registry.register(ResourceTypeInfo {
349 type_url: TypeUrl::VIRTUAL_HOST.to_string(),
350 short_name: "VirtualHost".to_string(),
351 description: "Virtual Host Discovery Service (VHDS)".to_string(),
352 });
353
354 registry
355 }
356
357 pub fn register(&self, info: ResourceTypeInfo) {
361 self.types.insert(info.type_url.clone(), info);
362 }
363
364 #[must_use]
368 pub fn get(&self, type_url: &str) -> Option<ResourceTypeInfo> {
369 self.types.get(type_url).map(|r| r.clone())
370 }
371
372 #[must_use]
374 pub fn contains(&self, type_url: &str) -> bool {
375 self.types.contains_key(type_url)
376 }
377
378 #[must_use]
380 pub fn type_urls(&self) -> Vec<String> {
381 self.types.iter().map(|r| r.key().clone()).collect()
382 }
383
384 #[must_use]
386 pub fn len(&self) -> usize {
387 self.types.len()
388 }
389
390 #[must_use]
392 pub fn is_empty(&self) -> bool {
393 self.types.is_empty()
394 }
395
396 pub fn validate(&self, type_url: &str) -> Result<(), String> {
400 if self.contains(type_url) {
401 Ok(())
402 } else {
403 let available: Vec<_> = self.type_urls();
404 Err(format!(
405 "Unknown resource type: {}. Available types: {:?}",
406 type_url, available
407 ))
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn test_any_resource() {
418 let any = prost_types::Any {
419 type_url: TypeUrl::CLUSTER.to_string(),
420 value: vec![1, 2, 3],
421 };
422
423 let resource = AnyResource::new(TypeUrl::CLUSTER, "my-cluster", any);
424 assert_eq!(resource.type_url(), TypeUrl::CLUSTER);
425 assert_eq!(resource.name(), "my-cluster");
426 assert!(resource.version().is_none());
427 }
428
429 #[test]
430 fn test_any_resource_with_version() {
431 let any = prost_types::Any {
432 type_url: TypeUrl::CLUSTER.to_string(),
433 value: vec![],
434 };
435
436 let resource = AnyResource::new(TypeUrl::CLUSTER, "my-cluster", any).with_version("v1");
437 assert_eq!(resource.version(), Some("v1"));
438 }
439
440 #[test]
441 fn test_registry_new() {
442 let registry = ResourceRegistry::new();
443 assert!(registry.is_empty());
444 }
445
446 #[test]
447 fn test_registry_with_envoy_types() {
448 let registry = ResourceRegistry::with_envoy_types();
449 assert!(!registry.is_empty());
450 assert!(registry.contains(TypeUrl::CLUSTER));
451 assert!(registry.contains(TypeUrl::ENDPOINT));
452 }
453
454 #[test]
455 fn test_registry_register() {
456 let mut registry = ResourceRegistry::new();
457 registry.register(ResourceTypeInfo {
458 type_url: "custom.type".to_string(),
459 short_name: "Custom".to_string(),
460 description: "Custom type".to_string(),
461 });
462
463 assert!(registry.contains("custom.type"));
464 assert_eq!(registry.len(), 1);
465 }
466
467 #[test]
468 fn test_registry_get() {
469 let registry = ResourceRegistry::with_envoy_types();
470 let info = registry.get(TypeUrl::CLUSTER);
471 assert!(info.is_some(), "CLUSTER type should be registered");
472 #[allow(clippy::unwrap_used)]
474 let info = info.unwrap();
475 assert_eq!(info.short_name, "Cluster");
476 }
477
478 #[test]
481 fn test_shared_registry_new() {
482 let registry = SharedResourceRegistry::new();
483 assert!(registry.is_empty());
484 }
485
486 #[test]
487 fn test_shared_registry_with_envoy_types() {
488 let registry = SharedResourceRegistry::with_envoy_types();
489 assert!(!registry.is_empty());
490 assert!(registry.contains(TypeUrl::CLUSTER));
491 assert!(registry.contains(TypeUrl::ENDPOINT));
492 assert!(registry.contains(TypeUrl::LISTENER));
493 assert!(registry.contains(TypeUrl::ROUTE));
494 assert!(registry.contains(TypeUrl::SECRET));
495 assert!(registry.contains(TypeUrl::RUNTIME));
496 assert!(registry.contains(TypeUrl::SCOPED_ROUTE));
497 assert!(registry.contains(TypeUrl::VIRTUAL_HOST));
498 assert_eq!(registry.len(), 8);
499 }
500
501 #[test]
502 fn test_shared_registry_register() {
503 let registry = SharedResourceRegistry::new();
504 registry.register(ResourceTypeInfo {
505 type_url: "custom.type".to_string(),
506 short_name: "Custom".to_string(),
507 description: "Custom type".to_string(),
508 });
509
510 assert!(registry.contains("custom.type"));
511 assert_eq!(registry.len(), 1);
512 }
513
514 #[test]
515 fn test_shared_registry_get() {
516 let registry = SharedResourceRegistry::with_envoy_types();
517 let info = registry.get(TypeUrl::CLUSTER);
518 assert!(info.is_some(), "CLUSTER type should be registered");
519 #[allow(clippy::unwrap_used)]
520 let info = info.unwrap();
521 assert_eq!(info.short_name, "Cluster");
522 }
523
524 #[test]
525 fn test_shared_registry_validate() {
526 let registry = SharedResourceRegistry::with_envoy_types();
527
528 assert!(registry.validate(TypeUrl::CLUSTER).is_ok());
530
531 let err = registry.validate("unknown.type").unwrap_err();
533 assert!(err.contains("Unknown resource type"));
534 assert!(err.contains("unknown.type"));
535 }
536
537 #[test]
538 fn test_shared_registry_thread_safety() {
539 use std::sync::Arc;
540 use std::thread;
541
542 let registry = Arc::new(SharedResourceRegistry::new());
543 let mut handles = vec![];
544
545 for i in 0..10 {
547 let reg = Arc::clone(®istry);
548 handles.push(thread::spawn(move || {
549 reg.register(ResourceTypeInfo {
550 type_url: format!("type.{}", i),
551 short_name: format!("Type{}", i),
552 description: format!("Type {} description", i),
553 });
554 }));
555 }
556
557 for handle in handles {
558 handle.join().expect("Thread panicked");
559 }
560
561 assert_eq!(registry.len(), 10);
562 }
563}