1use serde::{Deserialize, Serialize};
30use std::fmt;
31#[cfg(feature = "runtime")]
32use uuid::Uuid;
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
47#[serde(tag = "type", rename_all = "snake_case")]
48#[derive(Default)]
49pub enum Scope {
50 #[default]
52 Global,
53 Namespace(String),
55 Service {
57 namespace: String,
58 service: String,
59 },
60}
61
62impl Scope {
63 pub fn is_global(&self) -> bool {
65 matches!(self, Scope::Global)
66 }
67
68 pub fn is_namespace(&self) -> bool {
70 matches!(self, Scope::Namespace(_))
71 }
72
73 pub fn is_service(&self) -> bool {
75 matches!(self, Scope::Service { .. })
76 }
77
78 pub fn namespace(&self) -> Option<&str> {
80 match self {
81 Scope::Global => None,
82 Scope::Namespace(ns) => Some(ns),
83 Scope::Service { namespace, .. } => Some(namespace),
84 }
85 }
86
87 pub fn service(&self) -> Option<&str> {
89 match self {
90 Scope::Service { service, .. } => Some(service),
91 _ => None,
92 }
93 }
94
95 pub fn parent(&self) -> Option<Scope> {
97 match self {
98 Scope::Global => None,
99 Scope::Namespace(_) => Some(Scope::Global),
100 Scope::Service { namespace, .. } => Some(Scope::Namespace(namespace.clone())),
101 }
102 }
103
104 pub fn chain(&self) -> Vec<Scope> {
106 let mut chain = vec![self.clone()];
107 let mut current = self.clone();
108 while let Some(parent) = current.parent() {
109 chain.push(parent.clone());
110 current = parent;
111 }
112 chain
113 }
114}
115
116
117impl fmt::Display for Scope {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 match self {
120 Scope::Global => write!(f, "global"),
121 Scope::Namespace(ns) => write!(f, "namespace:{}", ns),
122 Scope::Service { namespace, service } => {
123 write!(f, "service:{}:{}", namespace, service)
124 }
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
143pub struct QualifiedId {
144 pub name: String,
146 pub scope: Scope,
148}
149
150impl QualifiedId {
151 pub fn new(name: impl Into<String>, scope: Scope) -> Self {
153 Self {
154 name: name.into(),
155 scope,
156 }
157 }
158
159 pub fn global(name: impl Into<String>) -> Self {
161 Self {
162 name: name.into(),
163 scope: Scope::Global,
164 }
165 }
166
167 pub fn namespaced(namespace: impl Into<String>, name: impl Into<String>) -> Self {
169 Self {
170 name: name.into(),
171 scope: Scope::Namespace(namespace.into()),
172 }
173 }
174
175 pub fn in_service(
177 namespace: impl Into<String>,
178 service: impl Into<String>,
179 name: impl Into<String>,
180 ) -> Self {
181 Self {
182 name: name.into(),
183 scope: Scope::Service {
184 namespace: namespace.into(),
185 service: service.into(),
186 },
187 }
188 }
189
190 pub fn canonical(&self) -> String {
197 match &self.scope {
198 Scope::Global => self.name.clone(),
199 Scope::Namespace(ns) => format!("{}:{}", ns, self.name),
200 Scope::Service { namespace, service } => {
201 format!("{}:{}:{}", namespace, service, self.name)
202 }
203 }
204 }
205
206 pub fn parse(s: &str) -> Self {
213 let parts: Vec<&str> = s.splitn(3, ':').collect();
214 match parts.as_slice() {
215 [name] => Self::global(*name),
216 [namespace, name] => Self::namespaced(*namespace, *name),
217 [namespace, service, name] => Self::in_service(*namespace, *service, *name),
218 _ => Self::global(s), }
220 }
221
222 pub fn is_global(&self) -> bool {
224 self.scope.is_global()
225 }
226
227 pub fn is_qualified(&self) -> bool {
229 !self.scope.is_global()
230 }
231
232 pub fn name(&self) -> &str {
234 &self.name
235 }
236
237 pub fn scope(&self) -> &Scope {
239 &self.scope
240 }
241}
242
243impl fmt::Display for QualifiedId {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 write!(f, "{}", self.canonical())
246 }
247}
248
249impl From<&str> for QualifiedId {
250 fn from(s: &str) -> Self {
251 Self::parse(s)
252 }
253}
254
255impl From<String> for QualifiedId {
256 fn from(s: String) -> Self {
257 Self::parse(&s)
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
270pub struct CorrelationId(String);
271
272impl CorrelationId {
273 #[cfg(feature = "runtime")]
275 pub fn new() -> Self {
276 Self(Uuid::new_v4().to_string())
277 }
278
279 pub fn from_string(s: impl Into<String>) -> Self {
281 Self(s.into())
282 }
283
284 pub fn as_str(&self) -> &str {
286 &self.0
287 }
288
289 pub fn into_string(self) -> String {
291 self.0
292 }
293}
294
295#[cfg(feature = "runtime")]
296impl Default for CorrelationId {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302impl fmt::Display for CorrelationId {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 write!(f, "{}", self.0)
305 }
306}
307
308impl From<String> for CorrelationId {
309 fn from(s: String) -> Self {
310 Self(s)
311 }
312}
313
314impl From<&str> for CorrelationId {
315 fn from(s: &str) -> Self {
316 Self(s.to_string())
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
325pub struct RequestId(String);
326
327impl RequestId {
328 #[cfg(feature = "runtime")]
330 pub fn new() -> Self {
331 Self(Uuid::new_v4().to_string())
332 }
333
334 pub fn as_str(&self) -> &str {
336 &self.0
337 }
338}
339
340#[cfg(feature = "runtime")]
341impl Default for RequestId {
342 fn default() -> Self {
343 Self::new()
344 }
345}
346
347impl fmt::Display for RequestId {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 write!(f, "{}", self.0)
350 }
351}
352
353#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
358pub struct RouteId(String);
359
360impl RouteId {
361 pub fn new(id: impl Into<String>) -> Self {
362 Self(id.into())
363 }
364
365 pub fn as_str(&self) -> &str {
366 &self.0
367 }
368}
369
370impl fmt::Display for RouteId {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372 write!(f, "{}", self.0)
373 }
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
381pub struct UpstreamId(String);
382
383impl UpstreamId {
384 pub fn new(id: impl Into<String>) -> Self {
385 Self(id.into())
386 }
387
388 pub fn as_str(&self) -> &str {
389 &self.0
390 }
391}
392
393impl fmt::Display for UpstreamId {
394 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395 write!(f, "{}", self.0)
396 }
397}
398
399#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
403pub struct AgentId(String);
404
405impl AgentId {
406 pub fn new(id: impl Into<String>) -> Self {
407 Self(id.into())
408 }
409
410 pub fn as_str(&self) -> &str {
411 &self.0
412 }
413}
414
415impl fmt::Display for AgentId {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 write!(f, "{}", self.0)
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
430 fn test_scope_global() {
431 let scope = Scope::Global;
432 assert!(scope.is_global());
433 assert!(!scope.is_namespace());
434 assert!(!scope.is_service());
435 assert_eq!(scope.namespace(), None);
436 assert_eq!(scope.service(), None);
437 assert_eq!(scope.parent(), None);
438 }
439
440 #[test]
441 fn test_scope_namespace() {
442 let scope = Scope::Namespace("api".to_string());
443 assert!(!scope.is_global());
444 assert!(scope.is_namespace());
445 assert!(!scope.is_service());
446 assert_eq!(scope.namespace(), Some("api"));
447 assert_eq!(scope.service(), None);
448 assert_eq!(scope.parent(), Some(Scope::Global));
449 }
450
451 #[test]
452 fn test_scope_service() {
453 let scope = Scope::Service {
454 namespace: "api".to_string(),
455 service: "payments".to_string(),
456 };
457 assert!(!scope.is_global());
458 assert!(!scope.is_namespace());
459 assert!(scope.is_service());
460 assert_eq!(scope.namespace(), Some("api"));
461 assert_eq!(scope.service(), Some("payments"));
462 assert_eq!(
463 scope.parent(),
464 Some(Scope::Namespace("api".to_string()))
465 );
466 }
467
468 #[test]
469 fn test_scope_chain() {
470 let service_scope = Scope::Service {
471 namespace: "api".to_string(),
472 service: "payments".to_string(),
473 };
474 let chain = service_scope.chain();
475 assert_eq!(chain.len(), 3);
476 assert_eq!(
477 chain[0],
478 Scope::Service {
479 namespace: "api".to_string(),
480 service: "payments".to_string()
481 }
482 );
483 assert_eq!(chain[1], Scope::Namespace("api".to_string()));
484 assert_eq!(chain[2], Scope::Global);
485 }
486
487 #[test]
488 fn test_scope_display() {
489 assert_eq!(Scope::Global.to_string(), "global");
490 assert_eq!(
491 Scope::Namespace("api".to_string()).to_string(),
492 "namespace:api"
493 );
494 assert_eq!(
495 Scope::Service {
496 namespace: "api".to_string(),
497 service: "payments".to_string()
498 }
499 .to_string(),
500 "service:api:payments"
501 );
502 }
503
504 #[test]
509 fn test_qualified_id_global() {
510 let qid = QualifiedId::global("backend");
511 assert_eq!(qid.name(), "backend");
512 assert_eq!(qid.scope(), &Scope::Global);
513 assert_eq!(qid.canonical(), "backend");
514 assert!(qid.is_global());
515 assert!(!qid.is_qualified());
516 }
517
518 #[test]
519 fn test_qualified_id_namespaced() {
520 let qid = QualifiedId::namespaced("api", "backend");
521 assert_eq!(qid.name(), "backend");
522 assert_eq!(qid.scope(), &Scope::Namespace("api".to_string()));
523 assert_eq!(qid.canonical(), "api:backend");
524 assert!(!qid.is_global());
525 assert!(qid.is_qualified());
526 }
527
528 #[test]
529 fn test_qualified_id_service() {
530 let qid = QualifiedId::in_service("api", "payments", "checkout");
531 assert_eq!(qid.name(), "checkout");
532 assert_eq!(
533 qid.scope(),
534 &Scope::Service {
535 namespace: "api".to_string(),
536 service: "payments".to_string()
537 }
538 );
539 assert_eq!(qid.canonical(), "api:payments:checkout");
540 assert!(!qid.is_global());
541 assert!(qid.is_qualified());
542 }
543
544 #[test]
545 fn test_qualified_id_parse_global() {
546 let qid = QualifiedId::parse("backend");
547 assert_eq!(qid.name(), "backend");
548 assert_eq!(qid.scope(), &Scope::Global);
549 }
550
551 #[test]
552 fn test_qualified_id_parse_namespaced() {
553 let qid = QualifiedId::parse("api:backend");
554 assert_eq!(qid.name(), "backend");
555 assert_eq!(qid.scope(), &Scope::Namespace("api".to_string()));
556 }
557
558 #[test]
559 fn test_qualified_id_parse_service() {
560 let qid = QualifiedId::parse("api:payments:checkout");
561 assert_eq!(qid.name(), "checkout");
562 assert_eq!(
563 qid.scope(),
564 &Scope::Service {
565 namespace: "api".to_string(),
566 service: "payments".to_string()
567 }
568 );
569 }
570
571 #[test]
572 fn test_qualified_id_parse_with_extra_colons() {
573 let qid = QualifiedId::parse("api:payments:item:with:colons");
575 assert_eq!(qid.name(), "item:with:colons");
576 assert_eq!(
577 qid.scope(),
578 &Scope::Service {
579 namespace: "api".to_string(),
580 service: "payments".to_string()
581 }
582 );
583 }
584
585 #[test]
586 fn test_qualified_id_from_str() {
587 let qid: QualifiedId = "api:backend".into();
588 assert_eq!(qid.canonical(), "api:backend");
589 }
590
591 #[test]
592 fn test_qualified_id_display() {
593 let qid = QualifiedId::in_service("ns", "svc", "resource");
594 assert_eq!(qid.to_string(), "ns:svc:resource");
595 }
596
597 #[test]
598 fn test_qualified_id_equality() {
599 let qid1 = QualifiedId::namespaced("api", "backend");
600 let qid2 = QualifiedId::parse("api:backend");
601 assert_eq!(qid1, qid2);
602 }
603
604 #[test]
605 fn test_qualified_id_hash() {
606 use std::collections::HashSet;
607
608 let mut set = HashSet::new();
609 set.insert(QualifiedId::global("backend"));
610 set.insert(QualifiedId::namespaced("api", "backend"));
611 set.insert(QualifiedId::in_service("api", "svc", "backend"));
612
613 assert_eq!(set.len(), 3);
615
616 assert!(set.contains(&QualifiedId::parse("api:backend")));
618 }
619
620 #[test]
625 #[cfg(feature = "runtime")]
626 fn test_correlation_id() {
627 let id1 = CorrelationId::new();
628 let id2 = CorrelationId::from_string("test-id");
629
630 assert_ne!(id1, id2);
631 assert_eq!(id2.as_str(), "test-id");
632 }
633
634 #[test]
635 fn test_route_id() {
636 let id = RouteId::new("my-route");
637 assert_eq!(id.as_str(), "my-route");
638 assert_eq!(id.to_string(), "my-route");
639 }
640
641 #[test]
642 fn test_upstream_id() {
643 let id = UpstreamId::new("backend-pool");
644 assert_eq!(id.as_str(), "backend-pool");
645 }
646
647 #[test]
648 fn test_agent_id() {
649 let id = AgentId::new("waf-agent");
650 assert_eq!(id.as_str(), "waf-agent");
651 }
652}