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