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 { namespace: String, service: String },
57}
58
59impl Scope {
60 pub fn is_global(&self) -> bool {
62 matches!(self, Scope::Global)
63 }
64
65 pub fn is_namespace(&self) -> bool {
67 matches!(self, Scope::Namespace(_))
68 }
69
70 pub fn is_service(&self) -> bool {
72 matches!(self, Scope::Service { .. })
73 }
74
75 pub fn namespace(&self) -> Option<&str> {
77 match self {
78 Scope::Global => None,
79 Scope::Namespace(ns) => Some(ns),
80 Scope::Service { namespace, .. } => Some(namespace),
81 }
82 }
83
84 pub fn service(&self) -> Option<&str> {
86 match self {
87 Scope::Service { service, .. } => Some(service),
88 _ => None,
89 }
90 }
91
92 pub fn parent(&self) -> Option<Scope> {
94 match self {
95 Scope::Global => None,
96 Scope::Namespace(_) => Some(Scope::Global),
97 Scope::Service { namespace, .. } => Some(Scope::Namespace(namespace.clone())),
98 }
99 }
100
101 pub fn chain(&self) -> Vec<Scope> {
103 let mut chain = vec![self.clone()];
104 let mut current = self.clone();
105 while let Some(parent) = current.parent() {
106 chain.push(parent.clone());
107 current = parent;
108 }
109 chain
110 }
111}
112
113impl fmt::Display for Scope {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self {
116 Scope::Global => write!(f, "global"),
117 Scope::Namespace(ns) => write!(f, "namespace:{}", ns),
118 Scope::Service { namespace, service } => {
119 write!(f, "service:{}:{}", namespace, service)
120 }
121 }
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
139pub struct QualifiedId {
140 pub name: String,
142 pub scope: Scope,
144}
145
146impl QualifiedId {
147 pub fn new(name: impl Into<String>, scope: Scope) -> Self {
149 Self {
150 name: name.into(),
151 scope,
152 }
153 }
154
155 pub fn global(name: impl Into<String>) -> Self {
157 Self {
158 name: name.into(),
159 scope: Scope::Global,
160 }
161 }
162
163 pub fn namespaced(namespace: impl Into<String>, name: impl Into<String>) -> Self {
165 Self {
166 name: name.into(),
167 scope: Scope::Namespace(namespace.into()),
168 }
169 }
170
171 pub fn in_service(
173 namespace: impl Into<String>,
174 service: impl Into<String>,
175 name: impl Into<String>,
176 ) -> Self {
177 Self {
178 name: name.into(),
179 scope: Scope::Service {
180 namespace: namespace.into(),
181 service: service.into(),
182 },
183 }
184 }
185
186 pub fn canonical(&self) -> String {
193 match &self.scope {
194 Scope::Global => self.name.clone(),
195 Scope::Namespace(ns) => format!("{}:{}", ns, self.name),
196 Scope::Service { namespace, service } => {
197 format!("{}:{}:{}", namespace, service, self.name)
198 }
199 }
200 }
201
202 pub fn parse(s: &str) -> Self {
209 let parts: Vec<&str> = s.splitn(3, ':').collect();
210 match parts.as_slice() {
211 [name] => Self::global(*name),
212 [namespace, name] => Self::namespaced(*namespace, *name),
213 [namespace, service, name] => Self::in_service(*namespace, *service, *name),
214 _ => Self::global(s), }
216 }
217
218 pub fn is_global(&self) -> bool {
220 self.scope.is_global()
221 }
222
223 pub fn is_qualified(&self) -> bool {
225 !self.scope.is_global()
226 }
227
228 pub fn name(&self) -> &str {
230 &self.name
231 }
232
233 pub fn scope(&self) -> &Scope {
235 &self.scope
236 }
237}
238
239impl fmt::Display for QualifiedId {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 write!(f, "{}", self.canonical())
242 }
243}
244
245impl From<&str> for QualifiedId {
246 fn from(s: &str) -> Self {
247 Self::parse(s)
248 }
249}
250
251impl From<String> for QualifiedId {
252 fn from(s: String) -> Self {
253 Self::parse(&s)
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
266pub struct CorrelationId(String);
267
268impl CorrelationId {
269 #[cfg(feature = "runtime")]
271 pub fn new() -> Self {
272 Self(Uuid::new_v4().to_string())
273 }
274
275 pub fn from_string(s: impl Into<String>) -> Self {
277 Self(s.into())
278 }
279
280 pub fn as_str(&self) -> &str {
282 &self.0
283 }
284
285 pub fn into_string(self) -> String {
287 self.0
288 }
289}
290
291#[cfg(feature = "runtime")]
292impl Default for CorrelationId {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298impl fmt::Display for CorrelationId {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 write!(f, "{}", self.0)
301 }
302}
303
304impl From<String> for CorrelationId {
305 fn from(s: String) -> Self {
306 Self(s)
307 }
308}
309
310impl From<&str> for CorrelationId {
311 fn from(s: &str) -> Self {
312 Self(s.to_string())
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
321pub struct RequestId(String);
322
323impl RequestId {
324 #[cfg(feature = "runtime")]
326 pub fn new() -> Self {
327 Self(Uuid::new_v4().to_string())
328 }
329
330 pub fn as_str(&self) -> &str {
332 &self.0
333 }
334}
335
336#[cfg(feature = "runtime")]
337impl Default for RequestId {
338 fn default() -> Self {
339 Self::new()
340 }
341}
342
343impl fmt::Display for RequestId {
344 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345 write!(f, "{}", self.0)
346 }
347}
348
349#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
354pub struct RouteId(String);
355
356impl RouteId {
357 pub fn new(id: impl Into<String>) -> Self {
358 Self(id.into())
359 }
360
361 pub fn as_str(&self) -> &str {
362 &self.0
363 }
364}
365
366impl fmt::Display for RouteId {
367 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368 write!(f, "{}", self.0)
369 }
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
377pub struct UpstreamId(String);
378
379impl UpstreamId {
380 pub fn new(id: impl Into<String>) -> Self {
381 Self(id.into())
382 }
383
384 pub fn as_str(&self) -> &str {
385 &self.0
386 }
387}
388
389impl fmt::Display for UpstreamId {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 write!(f, "{}", self.0)
392 }
393}
394
395#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
399pub struct AgentId(String);
400
401impl AgentId {
402 pub fn new(id: impl Into<String>) -> Self {
403 Self(id.into())
404 }
405
406 pub fn as_str(&self) -> &str {
407 &self.0
408 }
409}
410
411impl fmt::Display for AgentId {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 write!(f, "{}", self.0)
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
426 fn test_scope_global() {
427 let scope = Scope::Global;
428 assert!(scope.is_global());
429 assert!(!scope.is_namespace());
430 assert!(!scope.is_service());
431 assert_eq!(scope.namespace(), None);
432 assert_eq!(scope.service(), None);
433 assert_eq!(scope.parent(), None);
434 }
435
436 #[test]
437 fn test_scope_namespace() {
438 let scope = Scope::Namespace("api".to_string());
439 assert!(!scope.is_global());
440 assert!(scope.is_namespace());
441 assert!(!scope.is_service());
442 assert_eq!(scope.namespace(), Some("api"));
443 assert_eq!(scope.service(), None);
444 assert_eq!(scope.parent(), Some(Scope::Global));
445 }
446
447 #[test]
448 fn test_scope_service() {
449 let scope = Scope::Service {
450 namespace: "api".to_string(),
451 service: "payments".to_string(),
452 };
453 assert!(!scope.is_global());
454 assert!(!scope.is_namespace());
455 assert!(scope.is_service());
456 assert_eq!(scope.namespace(), Some("api"));
457 assert_eq!(scope.service(), Some("payments"));
458 assert_eq!(scope.parent(), Some(Scope::Namespace("api".to_string())));
459 }
460
461 #[test]
462 fn test_scope_chain() {
463 let service_scope = Scope::Service {
464 namespace: "api".to_string(),
465 service: "payments".to_string(),
466 };
467 let chain = service_scope.chain();
468 assert_eq!(chain.len(), 3);
469 assert_eq!(
470 chain[0],
471 Scope::Service {
472 namespace: "api".to_string(),
473 service: "payments".to_string()
474 }
475 );
476 assert_eq!(chain[1], Scope::Namespace("api".to_string()));
477 assert_eq!(chain[2], Scope::Global);
478 }
479
480 #[test]
481 fn test_scope_display() {
482 assert_eq!(Scope::Global.to_string(), "global");
483 assert_eq!(
484 Scope::Namespace("api".to_string()).to_string(),
485 "namespace:api"
486 );
487 assert_eq!(
488 Scope::Service {
489 namespace: "api".to_string(),
490 service: "payments".to_string()
491 }
492 .to_string(),
493 "service:api:payments"
494 );
495 }
496
497 #[test]
502 fn test_qualified_id_global() {
503 let qid = QualifiedId::global("backend");
504 assert_eq!(qid.name(), "backend");
505 assert_eq!(qid.scope(), &Scope::Global);
506 assert_eq!(qid.canonical(), "backend");
507 assert!(qid.is_global());
508 assert!(!qid.is_qualified());
509 }
510
511 #[test]
512 fn test_qualified_id_namespaced() {
513 let qid = QualifiedId::namespaced("api", "backend");
514 assert_eq!(qid.name(), "backend");
515 assert_eq!(qid.scope(), &Scope::Namespace("api".to_string()));
516 assert_eq!(qid.canonical(), "api:backend");
517 assert!(!qid.is_global());
518 assert!(qid.is_qualified());
519 }
520
521 #[test]
522 fn test_qualified_id_service() {
523 let qid = QualifiedId::in_service("api", "payments", "checkout");
524 assert_eq!(qid.name(), "checkout");
525 assert_eq!(
526 qid.scope(),
527 &Scope::Service {
528 namespace: "api".to_string(),
529 service: "payments".to_string()
530 }
531 );
532 assert_eq!(qid.canonical(), "api:payments:checkout");
533 assert!(!qid.is_global());
534 assert!(qid.is_qualified());
535 }
536
537 #[test]
538 fn test_qualified_id_parse_global() {
539 let qid = QualifiedId::parse("backend");
540 assert_eq!(qid.name(), "backend");
541 assert_eq!(qid.scope(), &Scope::Global);
542 }
543
544 #[test]
545 fn test_qualified_id_parse_namespaced() {
546 let qid = QualifiedId::parse("api:backend");
547 assert_eq!(qid.name(), "backend");
548 assert_eq!(qid.scope(), &Scope::Namespace("api".to_string()));
549 }
550
551 #[test]
552 fn test_qualified_id_parse_service() {
553 let qid = QualifiedId::parse("api:payments:checkout");
554 assert_eq!(qid.name(), "checkout");
555 assert_eq!(
556 qid.scope(),
557 &Scope::Service {
558 namespace: "api".to_string(),
559 service: "payments".to_string()
560 }
561 );
562 }
563
564 #[test]
565 fn test_qualified_id_parse_with_extra_colons() {
566 let qid = QualifiedId::parse("api:payments:item:with:colons");
568 assert_eq!(qid.name(), "item:with:colons");
569 assert_eq!(
570 qid.scope(),
571 &Scope::Service {
572 namespace: "api".to_string(),
573 service: "payments".to_string()
574 }
575 );
576 }
577
578 #[test]
579 fn test_qualified_id_from_str() {
580 let qid: QualifiedId = "api:backend".into();
581 assert_eq!(qid.canonical(), "api:backend");
582 }
583
584 #[test]
585 fn test_qualified_id_display() {
586 let qid = QualifiedId::in_service("ns", "svc", "resource");
587 assert_eq!(qid.to_string(), "ns:svc:resource");
588 }
589
590 #[test]
591 fn test_qualified_id_equality() {
592 let qid1 = QualifiedId::namespaced("api", "backend");
593 let qid2 = QualifiedId::parse("api:backend");
594 assert_eq!(qid1, qid2);
595 }
596
597 #[test]
598 fn test_qualified_id_hash() {
599 use std::collections::HashSet;
600
601 let mut set = HashSet::new();
602 set.insert(QualifiedId::global("backend"));
603 set.insert(QualifiedId::namespaced("api", "backend"));
604 set.insert(QualifiedId::in_service("api", "svc", "backend"));
605
606 assert_eq!(set.len(), 3);
608
609 assert!(set.contains(&QualifiedId::parse("api:backend")));
611 }
612
613 #[test]
618 #[cfg(feature = "runtime")]
619 fn test_correlation_id() {
620 let id1 = CorrelationId::new();
621 let id2 = CorrelationId::from_string("test-id");
622
623 assert_ne!(id1, id2);
624 assert_eq!(id2.as_str(), "test-id");
625 }
626
627 #[test]
628 fn test_route_id() {
629 let id = RouteId::new("my-route");
630 assert_eq!(id.as_str(), "my-route");
631 assert_eq!(id.to_string(), "my-route");
632 }
633
634 #[test]
635 fn test_upstream_id() {
636 let id = UpstreamId::new("backend-pool");
637 assert_eq!(id.as_str(), "backend-pool");
638 }
639
640 #[test]
641 fn test_agent_id() {
642 let id = AgentId::new("waf-agent");
643 assert_eq!(id.as_str(), "waf-agent");
644 }
645}