1use serde::{Deserialize, Serialize};
30use std::fmt;
31use uuid::Uuid;
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
46#[serde(tag = "type", rename_all = "snake_case")]
47pub enum Scope {
48 Global,
50 Namespace(String),
52 Service {
54 namespace: String,
55 service: String,
56 },
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 Default for Scope {
114 fn default() -> Self {
115 Scope::Global
116 }
117}
118
119impl fmt::Display for Scope {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self {
122 Scope::Global => write!(f, "global"),
123 Scope::Namespace(ns) => write!(f, "namespace:{}", ns),
124 Scope::Service { namespace, service } => {
125 write!(f, "service:{}:{}", namespace, service)
126 }
127 }
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
145pub struct QualifiedId {
146 pub name: String,
148 pub scope: Scope,
150}
151
152impl QualifiedId {
153 pub fn new(name: impl Into<String>, scope: Scope) -> Self {
155 Self {
156 name: name.into(),
157 scope,
158 }
159 }
160
161 pub fn global(name: impl Into<String>) -> Self {
163 Self {
164 name: name.into(),
165 scope: Scope::Global,
166 }
167 }
168
169 pub fn namespaced(namespace: impl Into<String>, name: impl Into<String>) -> Self {
171 Self {
172 name: name.into(),
173 scope: Scope::Namespace(namespace.into()),
174 }
175 }
176
177 pub fn in_service(
179 namespace: impl Into<String>,
180 service: impl Into<String>,
181 name: impl Into<String>,
182 ) -> Self {
183 Self {
184 name: name.into(),
185 scope: Scope::Service {
186 namespace: namespace.into(),
187 service: service.into(),
188 },
189 }
190 }
191
192 pub fn canonical(&self) -> String {
199 match &self.scope {
200 Scope::Global => self.name.clone(),
201 Scope::Namespace(ns) => format!("{}:{}", ns, self.name),
202 Scope::Service { namespace, service } => {
203 format!("{}:{}:{}", namespace, service, self.name)
204 }
205 }
206 }
207
208 pub fn parse(s: &str) -> Self {
215 let parts: Vec<&str> = s.splitn(3, ':').collect();
216 match parts.as_slice() {
217 [name] => Self::global(*name),
218 [namespace, name] => Self::namespaced(*namespace, *name),
219 [namespace, service, name] => Self::in_service(*namespace, *service, *name),
220 _ => Self::global(s), }
222 }
223
224 pub fn is_global(&self) -> bool {
226 self.scope.is_global()
227 }
228
229 pub fn is_qualified(&self) -> bool {
231 !self.scope.is_global()
232 }
233
234 pub fn name(&self) -> &str {
236 &self.name
237 }
238
239 pub fn scope(&self) -> &Scope {
241 &self.scope
242 }
243}
244
245impl fmt::Display for QualifiedId {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "{}", self.canonical())
248 }
249}
250
251impl From<&str> for QualifiedId {
252 fn from(s: &str) -> Self {
253 Self::parse(s)
254 }
255}
256
257impl From<String> for QualifiedId {
258 fn from(s: String) -> Self {
259 Self::parse(&s)
260 }
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
272pub struct CorrelationId(String);
273
274impl CorrelationId {
275 pub fn new() -> Self {
277 Self(Uuid::new_v4().to_string())
278 }
279
280 pub fn from_string(s: impl Into<String>) -> Self {
282 Self(s.into())
283 }
284
285 pub fn as_str(&self) -> &str {
287 &self.0
288 }
289
290 pub fn into_string(self) -> String {
292 self.0
293 }
294}
295
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 pub fn new() -> Self {
330 Self(Uuid::new_v4().to_string())
331 }
332
333 pub fn as_str(&self) -> &str {
335 &self.0
336 }
337}
338
339impl Default for RequestId {
340 fn default() -> Self {
341 Self::new()
342 }
343}
344
345impl fmt::Display for RequestId {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 write!(f, "{}", self.0)
348 }
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
356pub struct RouteId(String);
357
358impl RouteId {
359 pub fn new(id: impl Into<String>) -> Self {
360 Self(id.into())
361 }
362
363 pub fn as_str(&self) -> &str {
364 &self.0
365 }
366}
367
368impl fmt::Display for RouteId {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 write!(f, "{}", self.0)
371 }
372}
373
374#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
379pub struct UpstreamId(String);
380
381impl UpstreamId {
382 pub fn new(id: impl Into<String>) -> Self {
383 Self(id.into())
384 }
385
386 pub fn as_str(&self) -> &str {
387 &self.0
388 }
389}
390
391impl fmt::Display for UpstreamId {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 write!(f, "{}", self.0)
394 }
395}
396
397#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
401pub struct AgentId(String);
402
403impl AgentId {
404 pub fn new(id: impl Into<String>) -> Self {
405 Self(id.into())
406 }
407
408 pub fn as_str(&self) -> &str {
409 &self.0
410 }
411}
412
413impl fmt::Display for AgentId {
414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415 write!(f, "{}", self.0)
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[test]
428 fn test_scope_global() {
429 let scope = Scope::Global;
430 assert!(scope.is_global());
431 assert!(!scope.is_namespace());
432 assert!(!scope.is_service());
433 assert_eq!(scope.namespace(), None);
434 assert_eq!(scope.service(), None);
435 assert_eq!(scope.parent(), None);
436 }
437
438 #[test]
439 fn test_scope_namespace() {
440 let scope = Scope::Namespace("api".to_string());
441 assert!(!scope.is_global());
442 assert!(scope.is_namespace());
443 assert!(!scope.is_service());
444 assert_eq!(scope.namespace(), Some("api"));
445 assert_eq!(scope.service(), None);
446 assert_eq!(scope.parent(), Some(Scope::Global));
447 }
448
449 #[test]
450 fn test_scope_service() {
451 let scope = Scope::Service {
452 namespace: "api".to_string(),
453 service: "payments".to_string(),
454 };
455 assert!(!scope.is_global());
456 assert!(!scope.is_namespace());
457 assert!(scope.is_service());
458 assert_eq!(scope.namespace(), Some("api"));
459 assert_eq!(scope.service(), Some("payments"));
460 assert_eq!(
461 scope.parent(),
462 Some(Scope::Namespace("api".to_string()))
463 );
464 }
465
466 #[test]
467 fn test_scope_chain() {
468 let service_scope = Scope::Service {
469 namespace: "api".to_string(),
470 service: "payments".to_string(),
471 };
472 let chain = service_scope.chain();
473 assert_eq!(chain.len(), 3);
474 assert_eq!(
475 chain[0],
476 Scope::Service {
477 namespace: "api".to_string(),
478 service: "payments".to_string()
479 }
480 );
481 assert_eq!(chain[1], Scope::Namespace("api".to_string()));
482 assert_eq!(chain[2], Scope::Global);
483 }
484
485 #[test]
486 fn test_scope_display() {
487 assert_eq!(Scope::Global.to_string(), "global");
488 assert_eq!(
489 Scope::Namespace("api".to_string()).to_string(),
490 "namespace:api"
491 );
492 assert_eq!(
493 Scope::Service {
494 namespace: "api".to_string(),
495 service: "payments".to_string()
496 }
497 .to_string(),
498 "service:api:payments"
499 );
500 }
501
502 #[test]
507 fn test_qualified_id_global() {
508 let qid = QualifiedId::global("backend");
509 assert_eq!(qid.name(), "backend");
510 assert_eq!(qid.scope(), &Scope::Global);
511 assert_eq!(qid.canonical(), "backend");
512 assert!(qid.is_global());
513 assert!(!qid.is_qualified());
514 }
515
516 #[test]
517 fn test_qualified_id_namespaced() {
518 let qid = QualifiedId::namespaced("api", "backend");
519 assert_eq!(qid.name(), "backend");
520 assert_eq!(qid.scope(), &Scope::Namespace("api".to_string()));
521 assert_eq!(qid.canonical(), "api:backend");
522 assert!(!qid.is_global());
523 assert!(qid.is_qualified());
524 }
525
526 #[test]
527 fn test_qualified_id_service() {
528 let qid = QualifiedId::in_service("api", "payments", "checkout");
529 assert_eq!(qid.name(), "checkout");
530 assert_eq!(
531 qid.scope(),
532 &Scope::Service {
533 namespace: "api".to_string(),
534 service: "payments".to_string()
535 }
536 );
537 assert_eq!(qid.canonical(), "api:payments:checkout");
538 assert!(!qid.is_global());
539 assert!(qid.is_qualified());
540 }
541
542 #[test]
543 fn test_qualified_id_parse_global() {
544 let qid = QualifiedId::parse("backend");
545 assert_eq!(qid.name(), "backend");
546 assert_eq!(qid.scope(), &Scope::Global);
547 }
548
549 #[test]
550 fn test_qualified_id_parse_namespaced() {
551 let qid = QualifiedId::parse("api:backend");
552 assert_eq!(qid.name(), "backend");
553 assert_eq!(qid.scope(), &Scope::Namespace("api".to_string()));
554 }
555
556 #[test]
557 fn test_qualified_id_parse_service() {
558 let qid = QualifiedId::parse("api:payments:checkout");
559 assert_eq!(qid.name(), "checkout");
560 assert_eq!(
561 qid.scope(),
562 &Scope::Service {
563 namespace: "api".to_string(),
564 service: "payments".to_string()
565 }
566 );
567 }
568
569 #[test]
570 fn test_qualified_id_parse_with_extra_colons() {
571 let qid = QualifiedId::parse("api:payments:item:with:colons");
573 assert_eq!(qid.name(), "item:with:colons");
574 assert_eq!(
575 qid.scope(),
576 &Scope::Service {
577 namespace: "api".to_string(),
578 service: "payments".to_string()
579 }
580 );
581 }
582
583 #[test]
584 fn test_qualified_id_from_str() {
585 let qid: QualifiedId = "api:backend".into();
586 assert_eq!(qid.canonical(), "api:backend");
587 }
588
589 #[test]
590 fn test_qualified_id_display() {
591 let qid = QualifiedId::in_service("ns", "svc", "resource");
592 assert_eq!(qid.to_string(), "ns:svc:resource");
593 }
594
595 #[test]
596 fn test_qualified_id_equality() {
597 let qid1 = QualifiedId::namespaced("api", "backend");
598 let qid2 = QualifiedId::parse("api:backend");
599 assert_eq!(qid1, qid2);
600 }
601
602 #[test]
603 fn test_qualified_id_hash() {
604 use std::collections::HashSet;
605
606 let mut set = HashSet::new();
607 set.insert(QualifiedId::global("backend"));
608 set.insert(QualifiedId::namespaced("api", "backend"));
609 set.insert(QualifiedId::in_service("api", "svc", "backend"));
610
611 assert_eq!(set.len(), 3);
613
614 assert!(set.contains(&QualifiedId::parse("api:backend")));
616 }
617
618 #[test]
623 fn test_correlation_id() {
624 let id1 = CorrelationId::new();
625 let id2 = CorrelationId::from_string("test-id");
626
627 assert_ne!(id1, id2);
628 assert_eq!(id2.as_str(), "test-id");
629 }
630
631 #[test]
632 fn test_route_id() {
633 let id = RouteId::new("my-route");
634 assert_eq!(id.as_str(), "my-route");
635 assert_eq!(id.to_string(), "my-route");
636 }
637
638 #[test]
639 fn test_upstream_id() {
640 let id = UpstreamId::new("backend-pool");
641 assert_eq!(id.as_str(), "backend-pool");
642 }
643
644 #[test]
645 fn test_agent_id() {
646 let id = AgentId::new("waf-agent");
647 assert_eq!(id.as_str(), "waf-agent");
648 }
649}