1use std::cmp::Ordering;
18use std::fmt;
19use std::hash::Hash;
20use std::hash::Hasher;
21use std::str::FromStr;
22
23use serde::Deserialize;
24use serde::Serialize;
25use smol_str::SmolStr;
26
27use crate::port::Port;
28
29const MAX_LABEL_LEN: usize = 63;
31
32#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct Label(SmolStr);
36
37#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
39pub enum LabelError {
40 #[error("label must not be empty")]
42 Empty,
43 #[error("label exceeds 63 characters")]
45 TooLong,
46 #[error("label must start with a lowercase letter")]
48 InvalidStart,
49 #[error("label must end with a lowercase letter or digit")]
51 InvalidEnd,
52 #[error("label contains invalid character '{0}'")]
54 InvalidChar(char),
55}
56
57impl Label {
58 pub fn new(s: &str) -> Result<Self, LabelError> {
60 if s.is_empty() {
61 return Err(LabelError::Empty);
62 }
63 if s.len() > MAX_LABEL_LEN {
64 return Err(LabelError::TooLong);
65 }
66 let first = s.as_bytes()[0];
67 if !first.is_ascii_lowercase() {
68 return Err(LabelError::InvalidStart);
69 }
70 let last = s.as_bytes()[s.len() - 1];
71 if !last.is_ascii_lowercase() && !last.is_ascii_digit() {
72 return Err(LabelError::InvalidEnd);
73 }
74 for ch in s.chars() {
75 if !ch.is_ascii_lowercase() && !ch.is_ascii_digit() && ch != '-' {
76 return Err(LabelError::InvalidChar(ch));
77 }
78 }
79 Ok(Self(SmolStr::new(s)))
80 }
81
82 pub fn strip(s: &str) -> Self {
88 let lowered: String = s
89 .chars()
90 .filter_map(|ch| {
91 let ch = ch.to_ascii_lowercase();
92 if ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-' {
93 Some(ch)
94 } else {
95 None
96 }
97 })
98 .collect();
99
100 let trimmed = lowered.trim_start_matches(|c: char| !c.is_ascii_lowercase());
102 let trimmed =
104 trimmed.trim_end_matches(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit());
105
106 if trimmed.is_empty() {
107 return Self(SmolStr::new("nil"));
108 }
109
110 let truncated = if trimmed.len() > MAX_LABEL_LEN {
111 let t = &trimmed[..MAX_LABEL_LEN];
113 t.trim_end_matches(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit())
114 } else {
115 trimmed
116 };
117
118 if truncated.is_empty() {
119 Self(SmolStr::new("nil"))
120 } else {
121 Self(SmolStr::new(truncated))
122 }
123 }
124
125 pub fn as_str(&self) -> &str {
127 &self.0
128 }
129}
130
131impl fmt::Debug for Label {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 write!(f, "Label({:?})", self.0.as_str())
134 }
135}
136
137impl fmt::Display for Label {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.write_str(&self.0)
140 }
141}
142
143impl FromStr for Label {
144 type Err = LabelError;
145
146 fn from_str(s: &str) -> Result<Self, Self::Err> {
147 Self::new(s)
148 }
149}
150
151impl Serialize for Label {
152 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
153 self.0.as_str().serialize(serializer)
154 }
155}
156
157impl<'de> Deserialize<'de> for Label {
158 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
159 let s = String::deserialize(deserializer)?;
160 Label::new(&s).map_err(serde::de::Error::custom)
161 }
162}
163
164#[derive(Clone)]
166pub enum Uid {
167 Singleton(Label),
169 Instance(u64),
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
175pub enum UidParseError {
176 #[error("invalid label: {0}")]
178 InvalidLabel(#[from] LabelError),
179 #[error("invalid hex uid: {0}")]
181 InvalidHex(String),
182}
183
184impl Uid {
185 pub fn instance() -> Self {
187 Uid::Instance(rand::random())
188 }
189
190 pub fn singleton(label: Label) -> Self {
192 Uid::Singleton(label)
193 }
194}
195
196impl PartialEq for Uid {
197 fn eq(&self, other: &Self) -> bool {
198 match (self, other) {
199 (Uid::Singleton(a), Uid::Singleton(b)) => a == b,
200 (Uid::Instance(a), Uid::Instance(b)) => a == b,
201 _ => false,
202 }
203 }
204}
205
206impl Eq for Uid {}
207
208impl Hash for Uid {
209 fn hash<H: Hasher>(&self, state: &mut H) {
210 std::mem::discriminant(self).hash(state);
211 match self {
212 Uid::Singleton(label) => label.hash(state),
213 Uid::Instance(uid) => uid.hash(state),
214 }
215 }
216}
217
218impl PartialOrd for Uid {
219 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
220 Some(self.cmp(other))
221 }
222}
223
224impl Ord for Uid {
225 fn cmp(&self, other: &Self) -> Ordering {
226 match (self, other) {
227 (Uid::Singleton(a), Uid::Singleton(b)) => a.cmp(b),
228 (Uid::Singleton(_), Uid::Instance(_)) => Ordering::Less,
229 (Uid::Instance(_), Uid::Singleton(_)) => Ordering::Greater,
230 (Uid::Instance(a), Uid::Instance(b)) => a.cmp(b),
231 }
232 }
233}
234
235impl fmt::Debug for Uid {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 match self {
240 Uid::Singleton(label) => write!(f, "Uid(_{})", label),
241 Uid::Instance(uid) => write!(f, "Uid({:016x})", uid),
242 }
243 }
244}
245
246impl fmt::Display for Uid {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 match self {
249 Uid::Singleton(label) => write!(f, "_{label}"),
250 Uid::Instance(uid) => write!(f, "{uid:016x}"),
251 }
252 }
253}
254
255impl FromStr for Uid {
257 type Err = UidParseError;
258
259 fn from_str(s: &str) -> Result<Self, Self::Err> {
260 if let Some(rest) = s.strip_prefix('_') {
261 let label = Label::new(rest)?;
262 return Ok(Uid::Singleton(label));
263 }
264 let uid = parse_hex_uid(s)?;
265 Ok(Uid::Instance(uid))
266 }
267}
268
269fn parse_hex_uid(s: &str) -> Result<u64, UidParseError> {
270 if s.is_empty() || s.len() > 16 {
271 return Err(UidParseError::InvalidHex(s.to_string()));
272 }
273 for ch in s.chars() {
274 if !ch.is_ascii_hexdigit() {
275 return Err(UidParseError::InvalidHex(s.to_string()));
276 }
277 }
278 u64::from_str_radix(s, 16).map_err(|_| UidParseError::InvalidHex(s.to_string()))
279}
280
281impl Serialize for Uid {
282 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
283 serializer.serialize_str(&self.to_string())
284 }
285}
286
287impl<'de> Deserialize<'de> for Uid {
288 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
289 let s = String::deserialize(deserializer)?;
290 Uid::from_str(&s).map_err(serde::de::Error::custom)
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
296pub enum IdParseError {
297 #[error("invalid proc id: {0}")]
299 InvalidProcId(#[from] UidParseError),
300 #[error("invalid actor id: expected format `<actor_uid>.<proc_uid>`")]
302 InvalidActorIdFormat,
303 #[error("invalid actor uid: {0}")]
305 InvalidActorUid(UidParseError),
306 #[error("invalid proc uid in actor id: {0}")]
308 InvalidActorProcUid(UidParseError),
309 #[error("invalid port id: expected format `<actor_id>:<port>`")]
311 InvalidPortIdFormat,
312 #[error("invalid port: {0}")]
314 InvalidPort(String),
315}
316
317#[derive(Clone, Serialize, Deserialize)]
322pub struct ProcId {
323 uid: Uid,
324 label: Option<Label>,
325}
326
327impl ProcId {
328 pub fn new(uid: Uid, label: Option<Label>) -> Self {
330 Self { uid, label }
331 }
332
333 pub fn uid(&self) -> &Uid {
335 &self.uid
336 }
337
338 pub fn label(&self) -> Option<&Label> {
340 self.label.as_ref()
341 }
342}
343
344impl PartialEq for ProcId {
345 fn eq(&self, other: &Self) -> bool {
346 self.uid == other.uid
347 }
348}
349
350impl Eq for ProcId {}
351
352impl Hash for ProcId {
353 fn hash<H: Hasher>(&self, state: &mut H) {
354 self.uid.hash(state);
355 }
356}
357
358impl PartialOrd for ProcId {
359 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
360 Some(self.cmp(other))
361 }
362}
363
364impl Ord for ProcId {
365 fn cmp(&self, other: &Self) -> Ordering {
366 self.uid.cmp(&other.uid)
367 }
368}
369
370impl fmt::Display for ProcId {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372 write!(f, "{}", self.uid)
373 }
374}
375
376impl fmt::Debug for ProcId {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 match &self.label {
379 Some(label) => write!(f, "<'{}' {}>", label, self.uid),
380 None => write!(f, "<{}>", self.uid),
381 }
382 }
383}
384
385impl FromStr for ProcId {
386 type Err = IdParseError;
387
388 fn from_str(s: &str) -> Result<Self, Self::Err> {
389 let uid: Uid = s.parse()?;
390 Ok(Self { uid, label: None })
391 }
392}
393
394#[derive(Clone, Serialize, Deserialize)]
399pub struct ActorId {
400 uid: Uid,
401 proc_id: ProcId,
402 label: Option<Label>,
403}
404
405impl ActorId {
406 pub fn new(uid: Uid, proc_id: ProcId, label: Option<Label>) -> Self {
408 Self {
409 uid,
410 proc_id,
411 label,
412 }
413 }
414
415 pub fn uid(&self) -> &Uid {
417 &self.uid
418 }
419
420 pub fn proc_id(&self) -> &ProcId {
422 &self.proc_id
423 }
424
425 pub fn label(&self) -> Option<&Label> {
427 self.label.as_ref()
428 }
429}
430
431impl PartialEq for ActorId {
432 fn eq(&self, other: &Self) -> bool {
433 self.proc_id == other.proc_id && self.uid == other.uid
434 }
435}
436
437impl Eq for ActorId {}
438
439impl Hash for ActorId {
440 fn hash<H: Hasher>(&self, state: &mut H) {
441 self.proc_id.hash(state);
442 self.uid.hash(state);
443 }
444}
445
446impl PartialOrd for ActorId {
447 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
448 Some(self.cmp(other))
449 }
450}
451
452impl Ord for ActorId {
453 fn cmp(&self, other: &Self) -> Ordering {
454 self.proc_id
455 .cmp(&other.proc_id)
456 .then_with(|| self.uid.cmp(&other.uid))
457 }
458}
459
460impl fmt::Display for ActorId {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 write!(f, "{}.{}", self.uid, self.proc_id.uid)
463 }
464}
465
466impl fmt::Debug for ActorId {
467 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468 match (&self.label, &self.proc_id.label) {
469 (Some(actor_label), Some(proc_label)) => {
470 write!(
471 f,
472 "<'{}.{}' {}.{}>",
473 actor_label, proc_label, self.uid, self.proc_id.uid
474 )
475 }
476 (Some(actor_label), None) => {
477 write!(f, "<'{}' {}.{}>", actor_label, self.uid, self.proc_id.uid)
478 }
479 (None, Some(proc_label)) => {
480 write!(f, "<'.{}' {}.{}>", proc_label, self.uid, self.proc_id.uid)
481 }
482 (None, None) => {
483 write!(f, "<{}.{}>", self.uid, self.proc_id.uid)
484 }
485 }
486 }
487}
488
489impl FromStr for ActorId {
490 type Err = IdParseError;
491
492 fn from_str(s: &str) -> Result<Self, Self::Err> {
493 let dot = s.find('.').ok_or(IdParseError::InvalidActorIdFormat)?;
494 let actor_part = &s[..dot];
495 let proc_part = &s[dot + 1..];
496
497 let actor_uid: Uid = actor_part.parse().map_err(IdParseError::InvalidActorUid)?;
498 let proc_uid: Uid = proc_part
499 .parse()
500 .map_err(IdParseError::InvalidActorProcUid)?;
501
502 Ok(Self {
503 uid: actor_uid,
504 proc_id: ProcId {
505 uid: proc_uid,
506 label: None,
507 },
508 label: None,
509 })
510 }
511}
512
513#[derive(Clone, Serialize, Deserialize)]
517pub struct PortId {
518 actor_id: ActorId,
519 port: Port,
520}
521
522impl PortId {
523 pub fn new(actor_id: ActorId, port: Port) -> Self {
525 Self { actor_id, port }
526 }
527
528 pub fn actor_id(&self) -> &ActorId {
530 &self.actor_id
531 }
532
533 pub fn port(&self) -> Port {
535 self.port
536 }
537
538 pub fn proc_id(&self) -> &ProcId {
540 self.actor_id.proc_id()
541 }
542}
543
544impl PartialEq for PortId {
545 fn eq(&self, other: &Self) -> bool {
546 self.actor_id == other.actor_id && self.port == other.port
547 }
548}
549
550impl Eq for PortId {}
551
552impl Hash for PortId {
553 fn hash<H: Hasher>(&self, state: &mut H) {
554 self.actor_id.hash(state);
555 self.port.hash(state);
556 }
557}
558
559impl PartialOrd for PortId {
560 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
561 Some(self.cmp(other))
562 }
563}
564
565impl Ord for PortId {
566 fn cmp(&self, other: &Self) -> Ordering {
567 self.actor_id
568 .cmp(&other.actor_id)
569 .then_with(|| self.port.cmp(&other.port))
570 }
571}
572
573impl fmt::Display for PortId {
574 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575 write!(f, "{}:{}", self.actor_id, self.port)
576 }
577}
578
579impl fmt::Debug for PortId {
580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
581 match (self.actor_id.label(), self.actor_id.proc_id().label()) {
582 (Some(actor_label), Some(proc_label)) => {
583 write!(
584 f,
585 "<'{}.{}' {}:{}>",
586 actor_label, proc_label, self.actor_id, self.port
587 )
588 }
589 (Some(actor_label), None) => {
590 write!(f, "<'{}' {}:{}>", actor_label, self.actor_id, self.port)
591 }
592 (None, Some(proc_label)) => {
593 write!(f, "<'.{}' {}:{}>", proc_label, self.actor_id, self.port)
594 }
595 (None, None) => {
596 write!(f, "<{}:{}>", self.actor_id, self.port)
597 }
598 }
599 }
600}
601
602impl FromStr for PortId {
603 type Err = IdParseError;
604
605 fn from_str(s: &str) -> Result<Self, Self::Err> {
606 let colon = s.rfind(':').ok_or(IdParseError::InvalidPortIdFormat)?;
607 let actor_part = &s[..colon];
608 let port_part = &s[colon + 1..];
609
610 let actor_id: ActorId = actor_part.parse()?;
611 let port: Port = port_part
612 .parse()
613 .map_err(|_| IdParseError::InvalidPort(port_part.to_string()))?;
614
615 Ok(Self { actor_id, port })
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 #[test]
624 fn test_label_valid() {
625 assert!(Label::new("a").is_ok());
626 assert!(Label::new("abc").is_ok());
627 assert!(Label::new("my-service").is_ok());
628 assert!(Label::new("a1").is_ok());
629 assert!(Label::new("abc123").is_ok());
630 assert!(Label::new("a-b-c").is_ok());
631 }
632
633 #[test]
634 fn test_label_invalid_empty() {
635 assert_eq!(Label::new(""), Err(LabelError::Empty));
636 }
637
638 #[test]
639 fn test_label_invalid_too_long() {
640 let long = "a".repeat(64);
641 assert_eq!(Label::new(&long), Err(LabelError::TooLong));
642 let exact = "a".repeat(63);
644 assert!(Label::new(&exact).is_ok());
645 }
646
647 #[test]
648 fn test_label_invalid_bad_start() {
649 assert_eq!(Label::new("1abc"), Err(LabelError::InvalidStart));
650 assert_eq!(Label::new("-abc"), Err(LabelError::InvalidStart));
651 assert_eq!(Label::new("Abc"), Err(LabelError::InvalidStart));
652 }
653
654 #[test]
655 fn test_label_invalid_bad_end() {
656 assert_eq!(Label::new("abc-"), Err(LabelError::InvalidEnd));
657 }
658
659 #[test]
660 fn test_label_invalid_char() {
661 assert_eq!(Label::new("ab_c"), Err(LabelError::InvalidChar('_')));
662 assert_eq!(Label::new("ab.c"), Err(LabelError::InvalidChar('.')));
663 assert_eq!(Label::new("aBc"), Err(LabelError::InvalidChar('B')));
664 }
665
666 #[test]
667 fn test_label_strip() {
668 assert_eq!(Label::strip("Hello-World").as_str(), "hello-world");
669 assert_eq!(Label::strip("123abc").as_str(), "abc");
670 assert_eq!(Label::strip("---abc---").as_str(), "abc");
671 assert_eq!(Label::strip("").as_str(), "nil");
672 assert_eq!(Label::strip("123").as_str(), "nil");
673 assert_eq!(Label::strip("My_Service!").as_str(), "myservice");
674 }
675
676 #[test]
677 fn test_label_strip_truncation() {
678 let long = format!("a{}", "b".repeat(100));
679 let stripped = Label::strip(&long);
680 assert!(stripped.as_str().len() <= MAX_LABEL_LEN);
681 }
682
683 #[test]
684 fn test_label_display_fromstr_roundtrip() {
685 let label = Label::new("my-service").unwrap();
686 let s = label.to_string();
687 assert_eq!(s, "my-service");
688 let parsed: Label = s.parse().unwrap();
689 assert_eq!(label, parsed);
690 }
691
692 #[test]
693 fn test_label_serde_roundtrip() {
694 let label = Label::new("my-service").unwrap();
695 let json = serde_json::to_string(&label).unwrap();
696 assert_eq!(json, "\"my-service\"");
697 let parsed: Label = serde_json::from_str(&json).unwrap();
698 assert_eq!(label, parsed);
699 }
700
701 #[test]
702 fn test_singleton_display_parse() {
703 let uid = Uid::singleton(Label::new("my-actor").unwrap());
704 let s = uid.to_string();
705 assert_eq!(s, "_my-actor");
706 let parsed: Uid = s.parse().unwrap();
707 assert_eq!(uid, parsed);
708 }
709
710 #[test]
711 fn test_instance_display_parse() {
712 let uid = Uid::Instance(0xd5d54d7201103869);
713 let s = uid.to_string();
714 assert_eq!(s, "d5d54d7201103869");
715 let parsed: Uid = s.parse().unwrap();
716 assert_eq!(uid, parsed);
717 }
718
719 #[test]
720 fn test_ordering_singleton_lt_instance() {
721 let singleton = Uid::singleton(Label::new("zzz").unwrap());
722 let instance = Uid::Instance(0);
723 assert!(singleton < instance);
724 }
725
726 #[test]
727 fn test_ordering_singletons() {
728 let a = Uid::singleton(Label::new("aaa").unwrap());
729 let b = Uid::singleton(Label::new("bbb").unwrap());
730 assert!(a < b);
731 }
732
733 #[test]
734 fn test_ordering_instances() {
735 let a = Uid::Instance(1);
736 let b = Uid::Instance(2);
737 assert!(a < b);
738 }
739
740 #[test]
741 fn test_uid_serde_roundtrip() {
742 let uids = vec![
743 Uid::singleton(Label::new("my-actor").unwrap()),
744 Uid::Instance(0xabcdef0123456789),
745 Uid::Instance(1),
746 ];
747 for uid in uids {
748 let json = serde_json::to_string(&uid).unwrap();
749 let parsed: Uid = serde_json::from_str(&json).unwrap();
750 assert_eq!(uid, parsed);
751 }
752 }
753
754 #[test]
755 fn test_uid_parse_errors() {
756 assert!("".parse::<Uid>().is_err());
758 assert!("_".parse::<Uid>().is_err());
760 assert!("_123bad".parse::<Uid>().is_err());
761 assert!("xyz".parse::<Uid>().is_err());
763 assert!("00000000000000001".parse::<Uid>().is_err());
765 }
766
767 #[test]
768 fn test_unique_uid_generation() {
769 let a = Uid::instance();
770 let b = Uid::instance();
771 assert_ne!(a, b);
772 }
773
774 #[test]
775 fn test_short_hex_parse() {
776 let parsed: Uid = "1".parse().unwrap();
777 assert_eq!(parsed, Uid::Instance(1));
778 }
779
780 #[test]
781 fn test_proc_id_construction_and_accessors() {
782 let uid = Uid::Instance(0xabc);
783 let label = Label::new("my-proc").unwrap();
784 let pid = ProcId::new(uid.clone(), Some(label.clone()));
785 assert_eq!(pid.uid(), &uid);
786 assert_eq!(pid.label(), Some(&label));
787 }
788
789 #[test]
790 fn test_proc_id_eq_ignores_label() {
791 let uid = Uid::Instance(0x42);
792 let a = ProcId::new(uid.clone(), Some(Label::new("alpha").unwrap()));
793 let b = ProcId::new(uid, Some(Label::new("beta").unwrap()));
794 assert_eq!(a, b);
795 }
796
797 #[test]
798 fn test_proc_id_hash_ignores_label() {
799 use std::collections::hash_map::DefaultHasher;
800
801 let uid = Uid::Instance(0x42);
802 let a = ProcId::new(uid.clone(), Some(Label::new("alpha").unwrap()));
803 let b = ProcId::new(uid, Some(Label::new("beta").unwrap()));
804
805 let hash = |pid: &ProcId| {
806 let mut h = DefaultHasher::new();
807 pid.hash(&mut h);
808 h.finish()
809 };
810 assert_eq!(hash(&a), hash(&b));
811 }
812
813 #[test]
814 fn test_proc_id_ord_ignores_label() {
815 let a = ProcId::new(Uid::Instance(1), Some(Label::new("zzz").unwrap()));
816 let b = ProcId::new(Uid::Instance(2), Some(Label::new("aaa").unwrap()));
817 assert!(a < b);
818 }
819
820 #[test]
821 fn test_proc_id_display() {
822 let pid = ProcId::new(
823 Uid::Instance(0xd5d54d7201103869),
824 Some(Label::new("my-proc").unwrap()),
825 );
826 assert_eq!(pid.to_string(), "d5d54d7201103869");
827
828 let pid_singleton = ProcId::new(
829 Uid::singleton(Label::new("my-proc").unwrap()),
830 Some(Label::new("my-proc").unwrap()),
831 );
832 assert_eq!(pid_singleton.to_string(), "_my-proc");
833 }
834
835 #[test]
836 fn test_proc_id_debug() {
837 let pid = ProcId::new(
838 Uid::Instance(0xd5d54d7201103869),
839 Some(Label::new("my-proc").unwrap()),
840 );
841 assert_eq!(format!("{:?}", pid), "<'my-proc' d5d54d7201103869>");
842
843 let pid_no_label = ProcId::new(Uid::Instance(0xd5d54d7201103869), None);
844 assert_eq!(format!("{:?}", pid_no_label), "<d5d54d7201103869>");
845 }
846
847 #[test]
848 fn test_proc_id_fromstr_roundtrip() {
849 let pid = ProcId::new(
850 Uid::Instance(0xd5d54d7201103869),
851 Some(Label::new("my-proc").unwrap()),
852 );
853 let s = pid.to_string();
854 let parsed: ProcId = s.parse().unwrap();
855 assert_eq!(pid, parsed);
856 }
857
858 #[test]
859 fn test_proc_id_fromstr_singleton() {
860 let parsed: ProcId = "_my-proc".parse().unwrap();
861 assert_eq!(
862 *parsed.uid(),
863 Uid::singleton(Label::new("my-proc").unwrap())
864 );
865 assert_eq!(parsed.label(), None);
866 }
867
868 #[test]
869 fn test_proc_id_serde_roundtrip() {
870 let pid = ProcId::new(
871 Uid::Instance(0xabcdef),
872 Some(Label::new("my-proc").unwrap()),
873 );
874 let json = serde_json::to_string(&pid).unwrap();
875 let parsed: ProcId = serde_json::from_str(&json).unwrap();
876 assert_eq!(pid, parsed);
877 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-proc"));
878
879 let pid_none = ProcId::new(Uid::Instance(0xabcdef), None);
880 let json_none = serde_json::to_string(&pid_none).unwrap();
881 let parsed_none: ProcId = serde_json::from_str(&json_none).unwrap();
882 assert_eq!(parsed_none.label(), None);
883 }
884
885 #[test]
886 fn test_actor_id_construction_and_accessors() {
887 let actor_uid = Uid::Instance(0xabc);
888 let proc_id = ProcId::new(Uid::Instance(0xdef), Some(Label::new("my-proc").unwrap()));
889 let label = Label::new("my-actor").unwrap();
890 let aid = ActorId::new(actor_uid.clone(), proc_id.clone(), Some(label.clone()));
891 assert_eq!(aid.uid(), &actor_uid);
892 assert_eq!(aid.proc_id(), &proc_id);
893 assert_eq!(aid.label(), Some(&label));
894 }
895
896 #[test]
897 fn test_actor_id_eq_ignores_label() {
898 let actor_uid = Uid::Instance(0x42);
899 let proc_id = ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap()));
900 let a = ActorId::new(
901 actor_uid.clone(),
902 proc_id.clone(),
903 Some(Label::new("alpha").unwrap()),
904 );
905 let b = ActorId::new(actor_uid, proc_id, Some(Label::new("beta").unwrap()));
906 assert_eq!(a, b);
907 }
908
909 #[test]
910 fn test_actor_id_neq_different_proc() {
911 let actor_uid = Uid::Instance(0x42);
912 let proc_a = ProcId::new(Uid::Instance(1), Some(Label::new("proc").unwrap()));
913 let proc_b = ProcId::new(Uid::Instance(2), Some(Label::new("proc").unwrap()));
914 let a = ActorId::new(
915 actor_uid.clone(),
916 proc_a,
917 Some(Label::new("actor").unwrap()),
918 );
919 let b = ActorId::new(actor_uid, proc_b, Some(Label::new("actor").unwrap()));
920 assert_ne!(a, b);
921 }
922
923 #[test]
924 fn test_actor_id_hash_ignores_label() {
925 use std::collections::hash_map::DefaultHasher;
926
927 let actor_uid = Uid::Instance(0x42);
928 let proc_id = ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap()));
929 let a = ActorId::new(
930 actor_uid.clone(),
931 proc_id.clone(),
932 Some(Label::new("alpha").unwrap()),
933 );
934 let b = ActorId::new(actor_uid, proc_id, Some(Label::new("beta").unwrap()));
935
936 let hash = |aid: &ActorId| {
937 let mut h = DefaultHasher::new();
938 aid.hash(&mut h);
939 h.finish()
940 };
941 assert_eq!(hash(&a), hash(&b));
942 }
943
944 #[test]
945 fn test_actor_id_ord_proc_first() {
946 let a = ActorId::new(
947 Uid::Instance(0xff),
948 ProcId::new(Uid::Instance(1), Some(Label::new("p").unwrap())),
949 Some(Label::new("a").unwrap()),
950 );
951 let b = ActorId::new(
952 Uid::Instance(0x01),
953 ProcId::new(Uid::Instance(2), Some(Label::new("p").unwrap())),
954 Some(Label::new("a").unwrap()),
955 );
956 assert!(a < b, "proc_id should be compared first");
957 }
958
959 #[test]
960 fn test_actor_id_ord_then_uid() {
961 let proc_id = ProcId::new(Uid::Instance(1), Some(Label::new("p").unwrap()));
962 let a = ActorId::new(
963 Uid::Instance(1),
964 proc_id.clone(),
965 Some(Label::new("a").unwrap()),
966 );
967 let b = ActorId::new(Uid::Instance(2), proc_id, Some(Label::new("a").unwrap()));
968 assert!(a < b);
969 }
970
971 #[test]
972 fn test_actor_id_display() {
973 let aid = ActorId::new(
974 Uid::Instance(0xabc123),
975 ProcId::new(
976 Uid::Instance(0xdef456),
977 Some(Label::new("my-proc").unwrap()),
978 ),
979 Some(Label::new("my-actor").unwrap()),
980 );
981 assert_eq!(aid.to_string(), "0000000000abc123.0000000000def456");
982 }
983
984 #[test]
985 fn test_actor_id_debug() {
986 let aid = ActorId::new(
987 Uid::Instance(0xabc123),
988 ProcId::new(
989 Uid::Instance(0xdef456),
990 Some(Label::new("my-proc").unwrap()),
991 ),
992 Some(Label::new("my-actor").unwrap()),
993 );
994 assert_eq!(
995 format!("{:?}", aid),
996 "<'my-actor.my-proc' 0000000000abc123.0000000000def456>"
997 );
998
999 let aid_no_labels = ActorId::new(
1000 Uid::Instance(0xabc123),
1001 ProcId::new(Uid::Instance(0xdef456), None),
1002 None,
1003 );
1004 assert_eq!(
1005 format!("{:?}", aid_no_labels),
1006 "<0000000000abc123.0000000000def456>"
1007 );
1008 }
1009
1010 #[test]
1011 fn test_actor_id_fromstr_roundtrip() {
1012 let aid = ActorId::new(
1013 Uid::Instance(0xabc123),
1014 ProcId::new(
1015 Uid::Instance(0xdef456),
1016 Some(Label::new("my-proc").unwrap()),
1017 ),
1018 Some(Label::new("my-actor").unwrap()),
1019 );
1020 let s = aid.to_string();
1021 let parsed: ActorId = s.parse().unwrap();
1022 assert_eq!(aid, parsed);
1023 }
1024
1025 #[test]
1026 fn test_actor_id_fromstr_with_singletons() {
1027 let parsed: ActorId = "_my-actor._my-proc".parse().unwrap();
1028 assert_eq!(
1029 *parsed.uid(),
1030 Uid::singleton(Label::new("my-actor").unwrap())
1031 );
1032 assert_eq!(
1033 *parsed.proc_id().uid(),
1034 Uid::singleton(Label::new("my-proc").unwrap())
1035 );
1036 }
1037
1038 #[test]
1039 fn test_actor_id_fromstr_errors() {
1040 assert!("no-dot-here".parse::<ActorId>().is_err());
1041 assert!(".".parse::<ActorId>().is_err());
1042 assert!("abc.".parse::<ActorId>().is_err());
1043 assert!(".abc".parse::<ActorId>().is_err());
1044 }
1045
1046 #[test]
1047 fn test_actor_id_serde_roundtrip() {
1048 let aid = ActorId::new(
1049 Uid::Instance(0xabcdef),
1050 ProcId::new(
1051 Uid::Instance(0x123456),
1052 Some(Label::new("my-proc").unwrap()),
1053 ),
1054 Some(Label::new("my-actor").unwrap()),
1055 );
1056 let json = serde_json::to_string(&aid).unwrap();
1057 let parsed: ActorId = serde_json::from_str(&json).unwrap();
1058 assert_eq!(aid, parsed);
1059 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-actor"));
1060 assert_eq!(
1061 parsed.proc_id().label().map(|l| l.as_str()),
1062 Some("my-proc")
1063 );
1064 }
1065
1066 #[test]
1067 fn test_port_id_construction_and_accessors() {
1068 let actor_uid = Uid::Instance(0xabc);
1069 let proc_id = ProcId::new(Uid::Instance(0xdef), Some(Label::new("my-proc").unwrap()));
1070 let actor_id = ActorId::new(
1071 actor_uid,
1072 proc_id.clone(),
1073 Some(Label::new("my-actor").unwrap()),
1074 );
1075 let port = Port::from(42);
1076 let pid = PortId::new(actor_id.clone(), port);
1077 assert_eq!(pid.actor_id(), &actor_id);
1078 assert_eq!(pid.port(), port);
1079 assert_eq!(pid.proc_id(), &proc_id);
1080 }
1081
1082 #[test]
1083 fn test_port_id_eq() {
1084 let actor_id = ActorId::new(
1085 Uid::Instance(0x42),
1086 ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap())),
1087 Some(Label::new("actor").unwrap()),
1088 );
1089 let a = PortId::new(actor_id.clone(), Port::from(10));
1090 let b = PortId::new(actor_id, Port::from(10));
1091 assert_eq!(a, b);
1092 }
1093
1094 #[test]
1095 fn test_port_id_neq_different_port() {
1096 let actor_id = ActorId::new(
1097 Uid::Instance(0x42),
1098 ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap())),
1099 Some(Label::new("actor").unwrap()),
1100 );
1101 let a = PortId::new(actor_id.clone(), Port::from(10));
1102 let b = PortId::new(actor_id, Port::from(20));
1103 assert_ne!(a, b);
1104 }
1105
1106 #[test]
1107 fn test_port_id_hash() {
1108 use std::collections::hash_map::DefaultHasher;
1109
1110 let actor_id = ActorId::new(
1111 Uid::Instance(0x42),
1112 ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap())),
1113 Some(Label::new("actor").unwrap()),
1114 );
1115 let a = PortId::new(actor_id.clone(), Port::from(10));
1116 let b = PortId::new(actor_id, Port::from(10));
1117 let hash = |pid: &PortId| {
1118 let mut h = DefaultHasher::new();
1119 pid.hash(&mut h);
1120 h.finish()
1121 };
1122 assert_eq!(hash(&a), hash(&b));
1123 }
1124
1125 #[test]
1126 fn test_port_id_ord() {
1127 let actor_id = ActorId::new(
1128 Uid::Instance(0x42),
1129 ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap())),
1130 Some(Label::new("actor").unwrap()),
1131 );
1132 let a = PortId::new(actor_id.clone(), Port::from(1));
1133 let b = PortId::new(actor_id, Port::from(2));
1134 assert!(a < b);
1135 }
1136
1137 #[test]
1138 fn test_port_id_ord_actor_first() {
1139 let a = PortId::new(
1140 ActorId::new(
1141 Uid::Instance(0x01),
1142 ProcId::new(Uid::Instance(1), Some(Label::new("p").unwrap())),
1143 Some(Label::new("a").unwrap()),
1144 ),
1145 Port::from(99),
1146 );
1147 let b = PortId::new(
1148 ActorId::new(
1149 Uid::Instance(0x02),
1150 ProcId::new(Uid::Instance(1), Some(Label::new("p").unwrap())),
1151 Some(Label::new("a").unwrap()),
1152 ),
1153 Port::from(1),
1154 );
1155 assert!(a < b, "actor_id should be compared first");
1156 }
1157
1158 #[test]
1159 fn test_port_id_display() {
1160 let aid = ActorId::new(
1161 Uid::Instance(0xabc123),
1162 ProcId::new(
1163 Uid::Instance(0xdef456),
1164 Some(Label::new("my-proc").unwrap()),
1165 ),
1166 Some(Label::new("my-actor").unwrap()),
1167 );
1168 let pid = PortId::new(aid, Port::from(42));
1169 assert_eq!(pid.to_string(), "0000000000abc123.0000000000def456:42");
1170 }
1171
1172 #[test]
1173 fn test_port_id_debug_all_labels() {
1174 let aid = ActorId::new(
1175 Uid::Instance(0xabc123),
1176 ProcId::new(
1177 Uid::Instance(0xdef456),
1178 Some(Label::new("my-proc").unwrap()),
1179 ),
1180 Some(Label::new("my-actor").unwrap()),
1181 );
1182 let pid = PortId::new(aid, Port::from(42));
1183 assert_eq!(
1184 format!("{:?}", pid),
1185 "<'my-actor.my-proc' 0000000000abc123.0000000000def456:42>"
1186 );
1187 }
1188
1189 #[test]
1190 fn test_port_id_debug_no_labels() {
1191 let aid = ActorId::new(
1192 Uid::Instance(0xabc123),
1193 ProcId::new(Uid::Instance(0xdef456), None),
1194 None,
1195 );
1196 let pid = PortId::new(aid, Port::from(42));
1197 assert_eq!(
1198 format!("{:?}", pid),
1199 "<0000000000abc123.0000000000def456:42>"
1200 );
1201 }
1202
1203 #[test]
1204 fn test_port_id_debug_actor_label_only() {
1205 let aid = ActorId::new(
1206 Uid::Instance(0xabc123),
1207 ProcId::new(Uid::Instance(0xdef456), None),
1208 Some(Label::new("my-actor").unwrap()),
1209 );
1210 let pid = PortId::new(aid, Port::from(42));
1211 assert_eq!(
1212 format!("{:?}", pid),
1213 "<'my-actor' 0000000000abc123.0000000000def456:42>"
1214 );
1215 }
1216
1217 #[test]
1218 fn test_port_id_debug_proc_label_only() {
1219 let aid = ActorId::new(
1220 Uid::Instance(0xabc123),
1221 ProcId::new(
1222 Uid::Instance(0xdef456),
1223 Some(Label::new("my-proc").unwrap()),
1224 ),
1225 None,
1226 );
1227 let pid = PortId::new(aid, Port::from(42));
1228 assert_eq!(
1229 format!("{:?}", pid),
1230 "<'.my-proc' 0000000000abc123.0000000000def456:42>"
1231 );
1232 }
1233
1234 #[test]
1235 fn test_port_id_fromstr_roundtrip() {
1236 let aid = ActorId::new(
1237 Uid::Instance(0xabc123),
1238 ProcId::new(
1239 Uid::Instance(0xdef456),
1240 Some(Label::new("my-proc").unwrap()),
1241 ),
1242 Some(Label::new("my-actor").unwrap()),
1243 );
1244 let pid = PortId::new(aid, Port::from(42));
1245 let s = pid.to_string();
1246 let parsed: PortId = s.parse().unwrap();
1247 assert_eq!(pid, parsed);
1248 }
1249
1250 #[test]
1251 fn test_port_id_fromstr_errors() {
1252 assert!(
1254 "0000000000abc123.0000000000def456"
1255 .parse::<PortId>()
1256 .is_err()
1257 );
1258 assert!(
1260 "0000000000abc123.0000000000def456:notanumber"
1261 .parse::<PortId>()
1262 .is_err()
1263 );
1264 }
1265
1266 #[test]
1267 fn test_port_id_serde_roundtrip() {
1268 let aid = ActorId::new(
1269 Uid::Instance(0xabcdef),
1270 ProcId::new(
1271 Uid::Instance(0x123456),
1272 Some(Label::new("my-proc").unwrap()),
1273 ),
1274 Some(Label::new("my-actor").unwrap()),
1275 );
1276 let pid = PortId::new(aid, Port::from(42));
1277 let json = serde_json::to_string(&pid).unwrap();
1278 let parsed: PortId = serde_json::from_str(&json).unwrap();
1279 assert_eq!(pid, parsed);
1280 }
1281}