1use std::cmp::Ordering;
36use std::collections::hash_map::DefaultHasher;
37use std::fmt;
38use std::hash::Hash;
39use std::hash::Hasher;
40use std::path::Path;
41use std::path::PathBuf;
42use std::str::FromStr;
43
44use enum_as_inner::EnumAsInner;
45use serde::Deserialize;
46use serde::Serialize;
47use serde::de::EnumAccess;
48use serde::de::SeqAccess;
49use serde::de::VariantAccess;
50use serde::de::Visitor;
51use serde::ser::SerializeTupleVariant;
52use smol_str::SmolStr;
53
54use crate::addr::ActorAddr;
55use crate::addr::Addr;
56use crate::addr::Location;
57use crate::addr::PortAddr;
58use crate::addr::ProcAddr;
59use crate::parse::id::encode_base58_uid;
60use crate::port::Port;
61
62const MAX_LABEL_LEN: usize = 63;
64
65#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct Label(SmolStr);
70
71#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
73pub enum LabelError {
74 #[error("label must not be empty")]
76 Empty,
77 #[error("label exceeds 63 characters")]
79 TooLong,
80 #[error("label must start with a lowercase letter")]
82 InvalidStart,
83 #[error("label must end with a lowercase letter or digit")]
85 InvalidEnd,
86 #[error("label contains invalid character '{0}'")]
89 InvalidChar(char),
90}
91
92impl Label {
93 pub fn new(s: &str) -> Result<Self, LabelError> {
95 if s.is_empty() {
96 return Err(LabelError::Empty);
97 }
98 if s.len() > MAX_LABEL_LEN {
99 return Err(LabelError::TooLong);
100 }
101 let first = s.as_bytes()[0];
102 if !first.is_ascii_lowercase() {
103 return Err(LabelError::InvalidStart);
104 }
105 let last = s.as_bytes()[s.len() - 1];
106 if !last.is_ascii_lowercase() && !last.is_ascii_digit() {
107 return Err(LabelError::InvalidEnd);
108 }
109 for ch in s.chars() {
110 if !ch.is_ascii_lowercase() && !ch.is_ascii_digit() && ch != '-' && ch != '_' {
111 return Err(LabelError::InvalidChar(ch));
112 }
113 }
114 Ok(Self(SmolStr::new(s)))
115 }
116
117 pub fn strip(s: &str) -> Self {
123 let lowered: String = s
124 .chars()
125 .filter_map(|ch| {
126 let ch = ch.to_ascii_lowercase();
127 if ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-' || ch == '_' {
128 Some(ch)
129 } else {
130 None
131 }
132 })
133 .collect();
134
135 let trimmed = lowered.trim_start_matches(|c: char| !c.is_ascii_lowercase());
137 let trimmed =
139 trimmed.trim_end_matches(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit());
140
141 if trimmed.is_empty() {
142 return Self(SmolStr::new("nil"));
143 }
144
145 let truncated = if trimmed.len() > MAX_LABEL_LEN {
146 let t = &trimmed[..MAX_LABEL_LEN];
148 t.trim_end_matches(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit())
149 } else {
150 trimmed
151 };
152
153 if truncated.is_empty() {
154 Self(SmolStr::new("nil"))
155 } else {
156 Self(SmolStr::new(truncated))
157 }
158 }
159
160 pub fn as_str(&self) -> &str {
162 &self.0
163 }
164}
165
166impl fmt::Debug for Label {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 write!(f, "Label({:?})", self.0.as_str())
169 }
170}
171
172impl fmt::Display for Label {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.write_str(&self.0)
175 }
176}
177
178impl FromStr for Label {
179 type Err = LabelError;
180
181 fn from_str(s: &str) -> Result<Self, Self::Err> {
182 Self::new(s)
183 }
184}
185
186impl Serialize for Label {
187 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
188 self.0.as_str().serialize(serializer)
189 }
190}
191
192impl<'de> Deserialize<'de> for Label {
193 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
194 let s = String::deserialize(deserializer)?;
195 Label::new(&s).map_err(serde::de::Error::custom)
196 }
197}
198
199#[derive(Clone, EnumAsInner)]
204pub enum Uid {
205 Singleton(Label),
207 Instance(u64, Option<Label>),
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
213pub enum UidParseError {
214 #[error("invalid uid syntax: {0}")]
216 InvalidSyntax(String),
217 #[error("invalid label: {0}")]
219 InvalidLabel(#[from] LabelError),
220 #[error("invalid base58 uid: {0}")]
222 InvalidBase58(String),
223}
224
225impl Uid {
226 pub fn anonymous() -> Self {
228 Uid::Instance(rand::random(), None)
229 }
230
231 pub fn instance(label: Label) -> Self {
233 Uid::Instance(rand::random(), Some(label))
234 }
235
236 pub fn singleton(label: Label) -> Self {
238 Uid::Singleton(label)
239 }
240
241 pub fn label(&self) -> Option<&Label> {
246 match self {
247 Uid::Singleton(label) => Some(label),
248 Uid::Instance(_, label) => label.as_ref(),
249 }
250 }
251
252 pub fn instance_uid_base58(&self) -> Option<String> {
254 match self {
255 Uid::Singleton(_) => None,
256 Uid::Instance(uid, _) => Some(encode_base58_uid(*uid)),
257 }
258 }
259
260 pub fn parse_instance_uid_base58(s: &str) -> Result<u64, UidParseError> {
262 parse_base58_uid(s)
263 }
264
265 pub fn with_label(self, label: Option<Label>) -> Self {
269 match self {
270 Uid::Singleton(label) => Uid::Singleton(label),
271 Uid::Instance(uid, existing) => Uid::Instance(uid, label.or(existing)),
272 }
273 }
274}
275
276impl PartialEq for Uid {
277 fn eq(&self, other: &Self) -> bool {
278 match (self, other) {
279 (Uid::Singleton(a), Uid::Singleton(b)) => a == b,
280 (Uid::Instance(a, _), Uid::Instance(b, _)) => a == b,
281 _ => false,
282 }
283 }
284}
285
286impl Eq for Uid {}
287
288impl Hash for Uid {
289 fn hash<H: Hasher>(&self, state: &mut H) {
290 std::mem::discriminant(self).hash(state);
291 match self {
292 Uid::Singleton(label) => label.hash(state),
293 Uid::Instance(uid, _) => uid.hash(state),
294 }
295 }
296}
297
298impl PartialOrd for Uid {
299 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
300 Some(self.cmp(other))
301 }
302}
303
304impl Ord for Uid {
305 fn cmp(&self, other: &Self) -> Ordering {
306 match (self, other) {
307 (Uid::Singleton(a), Uid::Singleton(b)) => a.cmp(b),
308 (Uid::Singleton(_), Uid::Instance(_, _)) => Ordering::Less,
309 (Uid::Instance(_, _), Uid::Singleton(_)) => Ordering::Greater,
310 (Uid::Instance(a, _), Uid::Instance(b, _)) => a.cmp(b),
311 }
312 }
313}
314
315impl fmt::Debug for Uid {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 match self {
320 Uid::Singleton(label) => write!(f, "Uid({})", label),
321 Uid::Instance(uid, Some(label)) => {
322 write!(f, "Uid({}<{}>)", label, encode_base58_uid(*uid))
323 }
324 Uid::Instance(uid, None) => write!(f, "Uid(<{}>)", encode_base58_uid(*uid)),
325 }
326 }
327}
328
329impl fmt::Display for Uid {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 match self {
332 Uid::Singleton(label) => write!(f, "{label}"),
333 Uid::Instance(uid, Some(label)) => write!(f, "{}<{}>", label, encode_base58_uid(*uid)),
334 Uid::Instance(uid, None) => write!(f, "<{}>", encode_base58_uid(*uid)),
335 }
336 }
337}
338
339impl FromStr for Uid {
342 type Err = UidParseError;
343
344 fn from_str(s: &str) -> Result<Self, Self::Err> {
345 crate::parse::id::parse_uid_str(s)
346 .map_err(|err| UidParseError::InvalidSyntax(err.to_string()))
347 }
348}
349
350fn parse_base58_uid(s: &str) -> Result<u64, UidParseError> {
351 crate::parse::id::decode_base58_uid(s).map_err(|_| UidParseError::InvalidBase58(s.to_string()))
352}
353
354impl Serialize for Uid {
355 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
356 if serializer.is_human_readable() {
357 serializer.serialize_str(&self.to_string())
358 } else {
359 match self {
360 Uid::Singleton(label) => {
361 serializer.serialize_newtype_variant("Uid", 0, "Singleton", label)
362 }
363 Uid::Instance(uid, label) => {
364 let mut variant =
365 serializer.serialize_tuple_variant("Uid", 1, "Instance", 2)?;
366 variant.serialize_field(uid)?;
367 variant.serialize_field(label)?;
368 variant.end()
369 }
370 }
371 }
372 }
373}
374
375impl<'de> Deserialize<'de> for Uid {
376 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
377 if deserializer.is_human_readable() {
378 let s = String::deserialize(deserializer)?;
379 Uid::from_str(&s).map_err(serde::de::Error::custom)
380 } else {
381 deserializer.deserialize_enum("Uid", &["Singleton", "Instance"], UidVisitor)
382 }
383 }
384}
385
386struct UidVisitor;
387
388impl<'de> Visitor<'de> for UidVisitor {
389 type Value = Uid;
390
391 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 f.write_str("a uid enum")
393 }
394
395 fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
396 where
397 A: EnumAccess<'de>,
398 {
399 match data.variant()? {
400 (UidVariant::Singleton, variant) => variant.newtype_variant().map(Uid::Singleton),
401 (UidVariant::Instance, variant) => {
402 let (uid, label) = variant.tuple_variant(2, UidInstanceVisitor)?;
403 Ok(Uid::Instance(uid, label))
404 }
405 }
406 }
407}
408
409#[derive(Deserialize)]
410#[serde(field_identifier)]
411enum UidVariant {
412 Singleton,
413 Instance,
414}
415
416struct UidInstanceVisitor;
417
418impl<'de> Visitor<'de> for UidInstanceVisitor {
419 type Value = (u64, Option<Label>);
420
421 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 f.write_str("a uid instance tuple")
423 }
424
425 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
426 where
427 A: SeqAccess<'de>,
428 {
429 let uid = seq
430 .next_element()?
431 .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
432 let label = seq
433 .next_element()?
434 .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
435 Ok((uid, label))
436 }
437}
438
439#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
441pub enum IdParseError {
442 #[error("invalid proc id: {0}")]
444 InvalidProcId(#[from] UidParseError),
445 #[error("invalid actor id: expected format `<actor>.<proc>`")]
447 InvalidActorIdFormat,
448 #[error("invalid actor uid: {0}")]
450 InvalidActorUid(UidParseError),
451 #[error("invalid proc uid in actor id: {0}")]
453 InvalidActorProcUid(UidParseError),
454 #[error("invalid port id: expected format `<actor>:<port>`")]
456 InvalidPortIdFormat,
457 #[error("invalid port: {0}")]
459 InvalidPort(String),
460}
461
462#[derive(Clone, Serialize, Deserialize)]
466pub struct ProcId {
467 uid: Uid,
468}
469
470impl ProcId {
471 pub fn new(uid: Uid, label: Option<Label>) -> Self {
473 Self {
474 uid: uid.with_label(label),
475 }
476 }
477
478 pub fn anonymous() -> Self {
480 Self {
481 uid: Uid::anonymous(),
482 }
483 }
484
485 pub fn singleton(label: Label) -> Self {
487 Self {
488 uid: Uid::Singleton(label),
489 }
490 }
491
492 pub fn instance(label: Label) -> Self {
494 Self {
495 uid: Uid::instance(label),
496 }
497 }
498
499 pub fn uid(&self) -> &Uid {
501 &self.uid
502 }
503
504 pub fn label(&self) -> Option<&Label> {
506 self.uid.label()
507 }
508
509 pub fn to_path_elem(&self, base_dir: &Path) -> PathBuf {
518 let pseudo_id = self.pseudo_uid();
519 let tag = match pseudo_id {
520 Uid::Singleton(label) => {
521 panic!("pseudo uid should never be a singleton, but got: {}", label)
522 }
523 Uid::Instance(uid, _) => encode_base58_uid(uid).to_string(),
524 };
525 base_dir.join(tag)
526 }
527
528 pub fn pseudo_uid(&self) -> Uid {
537 match &self.uid {
538 Uid::Instance(_, _) => self.uid.clone(),
539 Uid::Singleton(label) => {
540 let mut h = DefaultHasher::new();
541 label.hash(&mut h);
542 Uid::Instance(h.finish(), None)
543 }
544 }
545 }
546}
547
548impl PartialEq for ProcId {
549 fn eq(&self, other: &Self) -> bool {
550 self.uid == other.uid
551 }
552}
553
554impl Eq for ProcId {}
555
556impl Hash for ProcId {
557 fn hash<H: Hasher>(&self, state: &mut H) {
558 self.uid.hash(state);
559 }
560}
561
562impl PartialOrd for ProcId {
563 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
564 Some(self.cmp(other))
565 }
566}
567
568impl Ord for ProcId {
569 fn cmp(&self, other: &Self) -> Ordering {
570 self.uid.cmp(&other.uid)
571 }
572}
573
574impl fmt::Display for ProcId {
575 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576 fmt::Display::fmt(&self.uid, f)
577 }
578}
579
580impl fmt::Debug for ProcId {
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 match self.label() {
583 Some(label) => write!(f, "<'{}' {}>", label, self.uid),
584 None => write!(f, "<{}>", self.uid),
585 }
586 }
587}
588
589impl FromStr for ProcId {
590 type Err = IdParseError;
591
592 fn from_str(s: &str) -> Result<Self, Self::Err> {
593 crate::parse::id::parse_proc_id(s).map_err(|err| {
594 IdParseError::InvalidProcId(UidParseError::InvalidSyntax(err.to_string()))
595 })
596 }
597}
598
599#[derive(Clone, Serialize, Deserialize)]
603pub struct ActorId {
604 uid: Uid,
605 proc_id: ProcId,
606}
607
608impl ActorId {
609 pub fn new(uid: Uid, proc_id: ProcId, label: Option<Label>) -> Self {
611 Self {
612 uid: uid.with_label(label),
613 proc_id,
614 }
615 }
616
617 pub fn singleton(label: Label, proc_id: ProcId) -> Self {
619 Self {
620 uid: Uid::Singleton(label),
621 proc_id,
622 }
623 }
624
625 pub fn anonymous(proc_id: ProcId) -> Self {
627 Self {
628 uid: Uid::anonymous(),
629 proc_id,
630 }
631 }
632
633 pub fn instance(label: Label, proc_id: ProcId) -> Self {
635 Self {
636 uid: Uid::instance(label),
637 proc_id,
638 }
639 }
640
641 pub fn uid(&self) -> &Uid {
643 &self.uid
644 }
645
646 pub fn proc_id(&self) -> &ProcId {
648 &self.proc_id
649 }
650
651 pub fn label(&self) -> Option<&Label> {
653 self.uid.label()
654 }
655}
656
657impl PartialEq for ActorId {
658 fn eq(&self, other: &Self) -> bool {
659 self.proc_id == other.proc_id && self.uid == other.uid
660 }
661}
662
663impl Eq for ActorId {}
664
665impl Hash for ActorId {
666 fn hash<H: Hasher>(&self, state: &mut H) {
667 self.proc_id.hash(state);
668 self.uid.hash(state);
669 }
670}
671
672impl PartialOrd for ActorId {
673 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
674 Some(self.cmp(other))
675 }
676}
677
678impl Ord for ActorId {
679 fn cmp(&self, other: &Self) -> Ordering {
680 self.proc_id
681 .cmp(&other.proc_id)
682 .then_with(|| self.uid.cmp(&other.uid))
683 }
684}
685
686impl fmt::Display for ActorId {
687 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
688 fmt::Display::fmt(&self.uid, f)?;
689 write!(f, ".{}", self.proc_id)
690 }
691}
692
693impl fmt::Debug for ActorId {
694 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
695 match (self.label(), self.proc_id.label()) {
696 (Some(actor_label), Some(proc_label)) => {
697 write!(
698 f,
699 "<'{}.{}' {}.{}>",
700 actor_label, proc_label, self.uid, self.proc_id.uid
701 )
702 }
703 (Some(actor_label), None) => {
704 write!(f, "<'{}' {}.{}>", actor_label, self.uid, self.proc_id.uid)
705 }
706 (None, Some(proc_label)) => {
707 write!(f, "<'.{}' {}.{}>", proc_label, self.uid, self.proc_id.uid)
708 }
709 (None, None) => {
710 write!(f, "<{}.{}>", self.uid, self.proc_id.uid)
711 }
712 }
713 }
714}
715
716impl FromStr for ActorId {
717 type Err = IdParseError;
718
719 fn from_str(s: &str) -> Result<Self, Self::Err> {
720 crate::parse::id::parse_actor_id(s).map_err(|_| legacy_parse_actor_id(s))
721 }
722}
723
724#[derive(Clone, Serialize, Deserialize)]
728pub struct PortId {
729 actor_id: ActorId,
730 port: Port,
731}
732
733impl PortId {
734 pub fn new(actor_id: ActorId, port: Port) -> Self {
736 Self { actor_id, port }
737 }
738
739 pub fn actor_id(&self) -> &ActorId {
741 &self.actor_id
742 }
743
744 pub fn port(&self) -> Port {
746 self.port
747 }
748
749 pub fn proc_id(&self) -> &ProcId {
751 self.actor_id.proc_id()
752 }
753}
754
755impl PartialEq for PortId {
756 fn eq(&self, other: &Self) -> bool {
757 self.actor_id == other.actor_id && self.port == other.port
758 }
759}
760
761impl Eq for PortId {}
762
763impl Hash for PortId {
764 fn hash<H: Hasher>(&self, state: &mut H) {
765 self.actor_id.hash(state);
766 self.port.hash(state);
767 }
768}
769
770impl PartialOrd for PortId {
771 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
772 Some(self.cmp(other))
773 }
774}
775
776impl Ord for PortId {
777 fn cmp(&self, other: &Self) -> Ordering {
778 self.actor_id
779 .cmp(&other.actor_id)
780 .then_with(|| self.port.cmp(&other.port))
781 }
782}
783
784impl fmt::Display for PortId {
785 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
786 write!(f, "{}:{}", self.actor_id, self.port)
787 }
788}
789
790impl fmt::Debug for PortId {
791 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
792 match (self.actor_id.label(), self.actor_id.proc_id().label()) {
793 (Some(actor_label), Some(proc_label)) => {
794 write!(
795 f,
796 "<'{}.{}' {}:{}>",
797 actor_label, proc_label, self.actor_id, self.port
798 )
799 }
800 (Some(actor_label), None) => {
801 write!(f, "<'{}' {}:{}>", actor_label, self.actor_id, self.port)
802 }
803 (None, Some(proc_label)) => {
804 write!(f, "<'.{}' {}:{}>", proc_label, self.actor_id, self.port)
805 }
806 (None, None) => {
807 write!(f, "<{}:{}>", self.actor_id, self.port)
808 }
809 }
810 }
811}
812
813impl FromStr for PortId {
814 type Err = IdParseError;
815
816 fn from_str(s: &str) -> Result<Self, Self::Err> {
817 crate::parse::id::parse_port_id(s).map_err(|_| legacy_port_parse_error(s))
818 }
819}
820
821#[derive(
823 Clone,
824 EnumAsInner,
825 PartialEq,
826 Eq,
827 Hash,
828 PartialOrd,
829 Ord,
830 Serialize,
831 Deserialize
832)]
833pub enum Id {
834 Proc(ProcId),
836 Actor(ActorId),
838 Port(PortId),
840}
841
842impl Id {
843 pub fn addr(self, location: Location) -> Addr {
845 match self {
846 Self::Proc(id) => Addr::Proc(ProcAddr::new(id, location)),
847 Self::Actor(id) => Addr::Actor(ActorAddr::new(id, location)),
848 Self::Port(id) => Addr::Port(PortAddr::new(id, location)),
849 }
850 }
851}
852
853impl fmt::Display for Id {
854 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
855 match self {
856 Self::Proc(id) => fmt::Display::fmt(id, f),
857 Self::Actor(id) => fmt::Display::fmt(id, f),
858 Self::Port(id) => fmt::Display::fmt(id, f),
859 }
860 }
861}
862
863impl fmt::Debug for Id {
864 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865 match self {
866 Self::Proc(id) => fmt::Debug::fmt(id, f),
867 Self::Actor(id) => fmt::Debug::fmt(id, f),
868 Self::Port(id) => fmt::Debug::fmt(id, f),
869 }
870 }
871}
872
873impl FromStr for Id {
874 type Err = IdParseError;
875
876 fn from_str(s: &str) -> Result<Self, Self::Err> {
877 crate::parse::id::parse_id(s).map_err(|_| legacy_parse_id(s))
878 }
879}
880
881fn legacy_parse_id_component(s: &str) -> Result<(Uid, Option<Label>), UidParseError> {
882 if let Some(inner) = s
883 .strip_prefix('<')
884 .and_then(|inner| inner.strip_suffix('>'))
885 {
886 let uid = parse_base58_uid(inner)?;
887 return Ok((Uid::Instance(uid, None), None));
888 }
889
890 if let Some(open) = s.find('<')
891 && s.ends_with('>')
892 {
893 let label = Label::new(&s[..open])?;
894 let uid = parse_base58_uid(&s[open + 1..s.len() - 1])?;
895 return Ok((Uid::Instance(uid, Some(label.clone())), Some(label)));
896 }
897
898 let label = Label::new(s)?;
899 Ok((Uid::Singleton(label.clone()), Some(label)))
900}
901
902fn legacy_parse_id(s: &str) -> IdParseError {
903 if s.contains(':') {
904 legacy_port_parse_error(s)
905 } else if s.contains('.') {
906 legacy_parse_actor_id(s)
907 } else {
908 legacy_parse_id_component(s)
909 .err()
910 .map(IdParseError::InvalidProcId)
911 .unwrap_or(IdParseError::InvalidActorIdFormat)
912 }
913}
914
915fn legacy_parse_actor_id(s: &str) -> IdParseError {
916 let Some((actor_part, proc_part)) = s.split_once('.') else {
917 return IdParseError::InvalidActorIdFormat;
918 };
919
920 if let Err(err) = legacy_parse_id_component(actor_part) {
921 return IdParseError::InvalidActorUid(err);
922 }
923
924 if let Err(err) = legacy_parse_id_component(proc_part) {
925 return IdParseError::InvalidActorProcUid(err);
926 }
927
928 IdParseError::InvalidActorIdFormat
929}
930
931fn legacy_port_parse_error(s: &str) -> IdParseError {
932 let Some((actor_part, port_part)) = s.split_once(':') else {
933 return IdParseError::InvalidPortIdFormat;
934 };
935
936 if crate::parse::id::parse_actor_id(actor_part).is_ok() {
937 return IdParseError::InvalidPort(port_part.to_string());
938 }
939
940 IdParseError::InvalidPortIdFormat
941}
942
943#[cfg(test)]
944mod tests {
945 use super::*;
946
947 #[test]
948 fn test_label_valid() {
949 assert!(Label::new("a").is_ok());
950 assert!(Label::new("abc").is_ok());
951 assert!(Label::new("my-service").is_ok());
952 assert!(Label::new("a1").is_ok());
953 assert!(Label::new("abc123").is_ok());
954 assert!(Label::new("a-b-c").is_ok());
955 }
956
957 #[test]
958 fn test_label_invalid_empty() {
959 assert_eq!(Label::new(""), Err(LabelError::Empty));
960 }
961
962 #[test]
963 fn test_label_invalid_too_long() {
964 let long = "a".repeat(64);
965 assert_eq!(Label::new(&long), Err(LabelError::TooLong));
966 let exact = "a".repeat(63);
968 assert!(Label::new(&exact).is_ok());
969 }
970
971 #[test]
972 fn test_label_invalid_bad_start() {
973 assert_eq!(Label::new("1abc"), Err(LabelError::InvalidStart));
974 assert_eq!(Label::new("-abc"), Err(LabelError::InvalidStart));
975 assert_eq!(Label::new("Abc"), Err(LabelError::InvalidStart));
976 }
977
978 #[test]
979 fn test_label_invalid_bad_end() {
980 assert_eq!(Label::new("abc-"), Err(LabelError::InvalidEnd));
981 }
982
983 #[test]
984 fn test_label_invalid_char() {
985 assert_eq!(Label::new("ab.c"), Err(LabelError::InvalidChar('.')));
986 assert_eq!(Label::new("aBc"), Err(LabelError::InvalidChar('B')));
987 }
988
989 #[test]
990 fn test_label_allows_underscores() {
991 assert!(Label::new("ab_c").is_ok());
992 assert!(Label::new("proc_agent").is_ok());
993 assert!(Label::new("host_agent").is_ok());
994 }
995
996 #[test]
997 fn test_label_strip() {
998 assert_eq!(Label::strip("Hello-World").as_str(), "hello-world");
999 assert_eq!(Label::strip("123abc").as_str(), "abc");
1000 assert_eq!(Label::strip("---abc---").as_str(), "abc");
1001 assert_eq!(Label::strip("").as_str(), "nil");
1002 assert_eq!(Label::strip("123").as_str(), "nil");
1003 assert_eq!(Label::strip("My_Service!").as_str(), "my_service");
1004 }
1005
1006 #[test]
1007 fn test_label_strip_truncation() {
1008 let long = format!("a{}", "b".repeat(100));
1009 let stripped = Label::strip(&long);
1010 assert!(stripped.as_str().len() <= MAX_LABEL_LEN);
1011 }
1012
1013 #[test]
1014 fn test_label_display_fromstr_roundtrip() {
1015 let label = Label::new("my-service").unwrap();
1016 let s = label.to_string();
1017 assert_eq!(s, "my-service");
1018 let parsed: Label = s.parse().unwrap();
1019 assert_eq!(label, parsed);
1020 }
1021
1022 #[test]
1023 fn test_label_serde_roundtrip() {
1024 let label = Label::new("my-service").unwrap();
1025 let json = serde_json::to_string(&label).unwrap();
1026 assert_eq!(json, "\"my-service\"");
1027 let parsed: Label = serde_json::from_str(&json).unwrap();
1028 assert_eq!(label, parsed);
1029 }
1030
1031 #[test]
1032 fn test_singleton_display_parse() {
1033 let uid = Uid::singleton(Label::new("my-actor").unwrap());
1034 let s = uid.to_string();
1035 assert_eq!(s, "my-actor");
1036 let parsed: Uid = s.parse().unwrap();
1037 assert_eq!(uid, parsed);
1038 }
1039
1040 #[test]
1041 fn test_instance_display_parse() {
1042 let uid = Uid::Instance(0xd5d54d7201103869, None);
1043 let s = uid.to_string();
1044 assert_eq!(s, format!("<{}>", encode_base58_uid(0xd5d54d7201103869)));
1045 assert_eq!(
1046 uid.instance_uid_base58(),
1047 Some(encode_base58_uid(0xd5d54d7201103869))
1048 );
1049 assert_eq!(
1050 Uid::parse_instance_uid_base58(&encode_base58_uid(0xd5d54d7201103869)),
1051 Ok(0xd5d54d7201103869)
1052 );
1053 let parsed: Uid = s.parse().unwrap();
1054 assert_eq!(uid, parsed);
1055 }
1056
1057 #[test]
1058 fn test_singleton_has_no_instance_base58() {
1059 let uid = Uid::singleton(Label::new("my-actor").unwrap());
1060 assert_eq!(uid.instance_uid_base58(), None);
1061 }
1062
1063 #[test]
1064 fn test_labeled_instance_display_parse() {
1065 let label = Label::new("my-actor").unwrap();
1066 let uid = Uid::Instance(0xd5d54d7201103869, Some(label.clone()));
1067 let s = uid.to_string();
1068 assert_eq!(
1069 s,
1070 format!("my-actor<{}>", encode_base58_uid(0xd5d54d7201103869))
1071 );
1072 let parsed: Uid = s.parse().unwrap();
1073 assert_eq!(parsed, uid);
1074 assert_eq!(parsed.label(), Some(&label));
1075 }
1076
1077 #[test]
1078 fn test_labeled_instance_identity_ignores_label() {
1079 let a = Uid::Instance(0x42, Some(Label::new("alpha").unwrap()));
1080 let b = Uid::Instance(0x42, Some(Label::new("beta").unwrap()));
1081 assert_eq!(a, b);
1082 assert_eq!(a.cmp(&b), Ordering::Equal);
1083
1084 use std::collections::hash_map::DefaultHasher;
1085
1086 let hash = |uid: &Uid| {
1087 let mut h = DefaultHasher::new();
1088 uid.hash(&mut h);
1089 h.finish()
1090 };
1091 assert_eq!(hash(&a), hash(&b));
1092 }
1093
1094 #[test]
1095 fn test_ordering_singleton_lt_instance() {
1096 let singleton = Uid::singleton(Label::new("zzz").unwrap());
1097 let instance = Uid::Instance(0, None);
1098 assert!(singleton < instance);
1099 }
1100
1101 #[test]
1102 fn test_ordering_singletons() {
1103 let a = Uid::singleton(Label::new("aaa").unwrap());
1104 let b = Uid::singleton(Label::new("bbb").unwrap());
1105 assert!(a < b);
1106 }
1107
1108 #[test]
1109 fn test_ordering_instances() {
1110 let a = Uid::Instance(1, None);
1111 let b = Uid::Instance(2, None);
1112 assert!(a < b);
1113 }
1114
1115 #[test]
1116 fn test_uid_serde_roundtrip() {
1117 let uids = vec![
1118 Uid::singleton(Label::new("my-actor").unwrap()),
1119 Uid::Instance(0xabcdef0123456789, None),
1120 Uid::Instance(1, None),
1121 Uid::Instance(0xd5d54d7201103869, Some(Label::new("my-actor").unwrap())),
1122 ];
1123 for uid in uids {
1124 let json = serde_json::to_string(&uid).unwrap();
1125 assert_eq!(json, format!("\"{}\"", uid));
1126 let parsed: Uid = serde_json::from_str(&json).unwrap();
1127 assert_eq!(uid, parsed);
1128
1129 let encoded = bincode::serde::encode_to_vec(&uid, bincode::config::legacy()).unwrap();
1130 let (parsed, len): (Uid, usize) =
1131 bincode::serde::decode_from_slice(&encoded, bincode::config::legacy()).unwrap();
1132 assert_eq!(len, encoded.len());
1133 assert_eq!(uid, parsed);
1134 }
1135 }
1136
1137 #[test]
1138 fn test_uid_parse_errors() {
1139 assert!("".parse::<Uid>().is_err());
1141 assert!("123bad".parse::<Uid>().is_err());
1143 assert!("<0>".parse::<Uid>().is_err());
1145 assert_eq!(
1147 "<abc".parse::<Uid>().unwrap_err().to_string(),
1148 "invalid uid syntax: expected \">\", found end of input"
1149 );
1150 }
1151
1152 #[test]
1153 fn test_unique_uid_generation() {
1154 let a = Uid::anonymous();
1155 let b = Uid::anonymous();
1156 assert_ne!(a, b);
1157 }
1158
1159 #[test]
1160 fn test_short_hex_parse() {
1161 let parsed: Uid = "<2>".parse().unwrap();
1162 assert_eq!(parsed, Uid::Instance(1, None));
1163 }
1164
1165 #[test]
1166 fn test_proc_id_construction_and_accessors() {
1167 let uid = Uid::Instance(0xabc, None);
1168 let label = Label::new("my-proc").unwrap();
1169 let pid = ProcId::new(uid.clone(), Some(label.clone()));
1170 assert_eq!(pid.uid(), &uid);
1171 assert_eq!(pid.label(), Some(&label));
1172 }
1173
1174 #[test]
1175 fn test_proc_id_eq_ignores_label() {
1176 let uid = Uid::Instance(0x42, None);
1177 let a = ProcId::new(uid.clone(), Some(Label::new("alpha").unwrap()));
1178 let b = ProcId::new(uid, Some(Label::new("beta").unwrap()));
1179 assert_eq!(a, b);
1180 }
1181
1182 #[test]
1183 fn test_proc_id_hash_ignores_label() {
1184 use std::collections::hash_map::DefaultHasher;
1185
1186 let uid = Uid::Instance(0x42, None);
1187 let a = ProcId::new(uid.clone(), Some(Label::new("alpha").unwrap()));
1188 let b = ProcId::new(uid, Some(Label::new("beta").unwrap()));
1189
1190 let hash = |pid: &ProcId| {
1191 let mut h = DefaultHasher::new();
1192 pid.hash(&mut h);
1193 h.finish()
1194 };
1195 assert_eq!(hash(&a), hash(&b));
1196 }
1197
1198 #[test]
1199 fn test_proc_id_ord_ignores_label() {
1200 let a = ProcId::new(Uid::Instance(1, None), Some(Label::new("zzz").unwrap()));
1201 let b = ProcId::new(Uid::Instance(2, None), Some(Label::new("aaa").unwrap()));
1202 assert!(a < b);
1203 }
1204
1205 #[test]
1206 fn test_proc_id_display() {
1207 let pid = ProcId::new(
1208 Uid::Instance(0xd5d54d7201103869, None),
1209 Some(Label::new("my-proc").unwrap()),
1210 );
1211 assert_eq!(
1212 pid.to_string(),
1213 format!("my-proc<{}>", encode_base58_uid(0xd5d54d7201103869))
1214 );
1215
1216 let pid_singleton = ProcId::new(
1217 Uid::singleton(Label::new("my-proc").unwrap()),
1218 Some(Label::new("my-proc").unwrap()),
1219 );
1220 assert_eq!(pid_singleton.to_string(), "my-proc");
1221 }
1222
1223 #[test]
1224 fn test_proc_id_debug() {
1225 let pid = ProcId::new(
1226 Uid::Instance(0xd5d54d7201103869, None),
1227 Some(Label::new("my-proc").unwrap()),
1228 );
1229 assert_eq!(
1230 format!("{:?}", pid),
1231 format!(
1232 "<'my-proc' my-proc<{}>>",
1233 encode_base58_uid(0xd5d54d7201103869)
1234 )
1235 );
1236
1237 let pid_no_label = ProcId::new(Uid::Instance(0xd5d54d7201103869, None), None);
1238 assert_eq!(
1239 format!("{:?}", pid_no_label),
1240 format!("<<{}>>", encode_base58_uid(0xd5d54d7201103869))
1241 );
1242 }
1243
1244 #[test]
1245 fn test_proc_id_fromstr_roundtrip() {
1246 let pid = ProcId::new(
1247 Uid::Instance(0xd5d54d7201103869, None),
1248 Some(Label::new("my-proc").unwrap()),
1249 );
1250 let s = pid.to_string();
1251 let parsed: ProcId = s.parse().unwrap();
1252 assert_eq!(pid, parsed);
1253 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-proc"));
1254 }
1255
1256 #[test]
1257 fn test_proc_id_fromstr_singleton() {
1258 let parsed: ProcId = "my-proc".parse().unwrap();
1259 assert_eq!(
1260 *parsed.uid(),
1261 Uid::singleton(Label::new("my-proc").unwrap())
1262 );
1263 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-proc"));
1264 }
1265
1266 #[test]
1267 fn test_proc_id_fromstr_unlabeled_instance() {
1268 let expected_uid = Uid::Instance(0xabc123, None);
1269 let parsed: ProcId = expected_uid.to_string().parse().unwrap();
1270 assert_eq!(parsed.uid(), &expected_uid);
1271 assert_eq!(parsed.label(), None);
1272 }
1273
1274 #[test]
1275 fn test_proc_id_fromstr_labeled_instance_with_underscore() {
1276 let expected_uid = Uid::Instance(0xabc123, None);
1277 let parsed: ProcId = format!("proc_agent{}", expected_uid).parse().unwrap();
1278 assert_eq!(parsed.uid(), &expected_uid);
1279 assert_eq!(
1280 parsed.label().map(|label| label.as_str()),
1281 Some("proc_agent")
1282 );
1283 }
1284
1285 #[test]
1286 fn test_proc_id_fromstr_errors_are_stable() {
1287 assert_eq!(
1288 "".parse::<ProcId>().unwrap_err().to_string(),
1289 "invalid proc id: invalid uid syntax: expected \"label\" or \"<\", found end of input"
1290 );
1291 assert_eq!(
1292 "controller<2MuAHeDjLCEd"
1293 .parse::<ProcId>()
1294 .unwrap_err()
1295 .to_string(),
1296 "invalid proc id: invalid uid syntax: expected \">\", found end of input"
1297 );
1298 assert_eq!(
1299 "controller@tcp".parse::<ProcId>().unwrap_err().to_string(),
1300 "invalid proc id: invalid uid syntax: expected end of input, found \"@\""
1301 );
1302 }
1303
1304 #[test]
1305 fn test_proc_id_serde_roundtrip() {
1306 let pid = ProcId::new(
1307 Uid::Instance(0xabcdef, None),
1308 Some(Label::new("my-proc").unwrap()),
1309 );
1310 let json = serde_json::to_string(&pid).unwrap();
1311 let parsed: ProcId = serde_json::from_str(&json).unwrap();
1312 assert_eq!(pid, parsed);
1313 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-proc"));
1314
1315 let pid_none = ProcId::new(Uid::Instance(0xabcdef, None), None);
1316 let json_none = serde_json::to_string(&pid_none).unwrap();
1317 let parsed_none: ProcId = serde_json::from_str(&json_none).unwrap();
1318 assert_eq!(parsed_none.label(), None);
1319 }
1320
1321 #[test]
1322 fn test_proc_id_singleton() {
1323 let label = Label::new("my-proc").unwrap();
1324 let pid = ProcId::singleton(label.clone());
1325 assert_eq!(*pid.uid(), Uid::Singleton(label.clone()));
1326 assert_eq!(pid.label(), Some(&label));
1327 }
1328
1329 #[test]
1330 fn test_proc_id_instance() {
1331 let label = Label::new("my-proc").unwrap();
1332 let pid = ProcId::instance(label.clone());
1333 assert!(pid.uid().is_instance());
1334 assert_eq!(pid.label(), Some(&label));
1335 let pid2 = ProcId::instance(label);
1336 assert_ne!(pid, pid2);
1337 }
1338
1339 #[test]
1340 fn test_proc_id_pseudo_uid_instance_returns_real_uid() {
1341 let uid = Uid::Instance(0xd5d54d7201103869, None);
1342 let pid = ProcId::new(uid.clone(), Some(Label::new("my-proc").unwrap()));
1343 assert_eq!(pid.pseudo_uid(), uid);
1344 }
1345
1346 #[test]
1347 fn test_proc_id_pseudo_uid_singleton_is_instance_form() {
1348 let pid = ProcId::singleton(Label::new("my-proc").unwrap());
1349 assert!(matches!(pid.pseudo_uid(), Uid::Instance(_, _)));
1350 }
1351
1352 #[test]
1353 fn test_proc_id_pseudo_uid_singleton_is_deterministic() {
1354 let a = ProcId::singleton(Label::new("my-proc").unwrap());
1355 let b = ProcId::singleton(Label::new("my-proc").unwrap());
1356 assert_eq!(a.pseudo_uid(), b.pseudo_uid());
1357 }
1358
1359 #[test]
1360 fn test_proc_id_pseudo_uid_singleton_distinct_labels_differ() {
1361 let a = ProcId::singleton(Label::new("alpha").unwrap());
1362 let b = ProcId::singleton(Label::new("beta").unwrap());
1363 assert_ne!(a.pseudo_uid(), b.pseudo_uid());
1364 }
1365
1366 #[test]
1367 fn test_proc_id_pseudo_uid_displays_as_short_base58() {
1368 let pid = ProcId::singleton(Label::new("my-proc").unwrap());
1369 let s = pid.pseudo_uid().to_string();
1370 assert!(s.starts_with('<') && s.ends_with('>'), "got: {s}");
1371 assert!(s.len() <= 13, "expected short base58 form, got: {s}");
1373 }
1374
1375 #[test]
1376 fn test_actor_id_singleton() {
1377 let label = Label::new("my-actor").unwrap();
1378 let proc_id = ProcId::singleton(Label::new("my-proc").unwrap());
1379 let aid = ActorId::singleton(label.clone(), proc_id.clone());
1380 assert_eq!(*aid.uid(), Uid::Singleton(label.clone()));
1381 assert_eq!(aid.proc_id(), &proc_id);
1382 assert_eq!(aid.label(), Some(&label));
1383 }
1384
1385 #[test]
1386 fn test_actor_id_anonymous() {
1387 let proc_id = ProcId::singleton(Label::new("my-proc").unwrap());
1388 let aid = ActorId::anonymous(proc_id.clone());
1389 assert!(aid.uid().is_instance());
1390 assert_eq!(aid.proc_id(), &proc_id);
1391 assert_eq!(aid.label(), None);
1392 let aid2 = ActorId::anonymous(proc_id);
1393 assert_ne!(aid, aid2);
1394 }
1395
1396 #[test]
1397 fn test_actor_id_instance() {
1398 let label = Label::new("my-actor").unwrap();
1399 let proc_id = ProcId::singleton(Label::new("my-proc").unwrap());
1400 let aid = ActorId::instance(label.clone(), proc_id.clone());
1401 assert!(aid.uid().is_instance());
1402 assert_eq!(aid.proc_id(), &proc_id);
1403 assert_eq!(aid.label(), Some(&label));
1404 let aid2 = ActorId::instance(label, proc_id);
1405 assert_ne!(aid, aid2);
1406 }
1407
1408 #[test]
1409 fn test_actor_id_construction_and_accessors() {
1410 let actor_uid = Uid::Instance(0xabc, None);
1411 let proc_id = ProcId::new(
1412 Uid::Instance(0xdef, None),
1413 Some(Label::new("my-proc").unwrap()),
1414 );
1415 let label = Label::new("my-actor").unwrap();
1416 let aid = ActorId::new(actor_uid.clone(), proc_id.clone(), Some(label.clone()));
1417 assert_eq!(aid.uid(), &actor_uid);
1418 assert_eq!(aid.proc_id(), &proc_id);
1419 assert_eq!(aid.label(), Some(&label));
1420 }
1421
1422 #[test]
1423 fn test_actor_id_eq_ignores_label() {
1424 let actor_uid = Uid::Instance(0x42, None);
1425 let proc_id = ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap()));
1426 let a = ActorId::new(
1427 actor_uid.clone(),
1428 proc_id.clone(),
1429 Some(Label::new("alpha").unwrap()),
1430 );
1431 let b = ActorId::new(actor_uid, proc_id, Some(Label::new("beta").unwrap()));
1432 assert_eq!(a, b);
1433 }
1434
1435 #[test]
1436 fn test_actor_id_neq_different_proc() {
1437 let actor_uid = Uid::Instance(0x42, None);
1438 let proc_a = ProcId::new(Uid::Instance(1, None), Some(Label::new("proc").unwrap()));
1439 let proc_b = ProcId::new(Uid::Instance(2, None), Some(Label::new("proc").unwrap()));
1440 let a = ActorId::new(
1441 actor_uid.clone(),
1442 proc_a,
1443 Some(Label::new("actor").unwrap()),
1444 );
1445 let b = ActorId::new(actor_uid, proc_b, Some(Label::new("actor").unwrap()));
1446 assert_ne!(a, b);
1447 }
1448
1449 #[test]
1450 fn test_actor_id_hash_ignores_label() {
1451 use std::collections::hash_map::DefaultHasher;
1452
1453 let actor_uid = Uid::Instance(0x42, None);
1454 let proc_id = ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap()));
1455 let a = ActorId::new(
1456 actor_uid.clone(),
1457 proc_id.clone(),
1458 Some(Label::new("alpha").unwrap()),
1459 );
1460 let b = ActorId::new(actor_uid, proc_id, Some(Label::new("beta").unwrap()));
1461
1462 let hash = |aid: &ActorId| {
1463 let mut h = DefaultHasher::new();
1464 aid.hash(&mut h);
1465 h.finish()
1466 };
1467 assert_eq!(hash(&a), hash(&b));
1468 }
1469
1470 #[test]
1471 fn test_actor_id_ord_proc_first() {
1472 let a = ActorId::new(
1473 Uid::Instance(0xff, None),
1474 ProcId::new(Uid::Instance(1, None), Some(Label::new("p").unwrap())),
1475 Some(Label::new("a").unwrap()),
1476 );
1477 let b = ActorId::new(
1478 Uid::Instance(0x01, None),
1479 ProcId::new(Uid::Instance(2, None), Some(Label::new("p").unwrap())),
1480 Some(Label::new("a").unwrap()),
1481 );
1482 assert!(a < b, "proc_id should be compared first");
1483 }
1484
1485 #[test]
1486 fn test_actor_id_ord_then_uid() {
1487 let proc_id = ProcId::new(Uid::Instance(1, None), Some(Label::new("p").unwrap()));
1488 let a = ActorId::new(
1489 Uid::Instance(1, None),
1490 proc_id.clone(),
1491 Some(Label::new("a").unwrap()),
1492 );
1493 let b = ActorId::new(
1494 Uid::Instance(2, None),
1495 proc_id,
1496 Some(Label::new("a").unwrap()),
1497 );
1498 assert!(a < b);
1499 }
1500
1501 #[test]
1502 fn test_actor_id_display() {
1503 let aid = ActorId::new(
1504 Uid::Instance(0xabc123, None),
1505 ProcId::new(
1506 Uid::Instance(0xdef456, None),
1507 Some(Label::new("my-proc").unwrap()),
1508 ),
1509 Some(Label::new("my-actor").unwrap()),
1510 );
1511 assert_eq!(
1512 aid.to_string(),
1513 format!(
1514 "my-actor<{}>.my-proc<{}>",
1515 encode_base58_uid(0xabc123),
1516 encode_base58_uid(0xdef456)
1517 )
1518 );
1519 }
1520
1521 #[test]
1522 fn test_actor_id_debug() {
1523 let aid = ActorId::new(
1524 Uid::Instance(0xabc123, None),
1525 ProcId::new(
1526 Uid::Instance(0xdef456, None),
1527 Some(Label::new("my-proc").unwrap()),
1528 ),
1529 Some(Label::new("my-actor").unwrap()),
1530 );
1531 assert_eq!(
1532 format!("{:?}", aid),
1533 format!(
1534 "<'my-actor.my-proc' my-actor<{}>.my-proc<{}>>",
1535 encode_base58_uid(0xabc123),
1536 encode_base58_uid(0xdef456)
1537 )
1538 );
1539
1540 let aid_no_labels = ActorId::new(
1541 Uid::Instance(0xabc123, None),
1542 ProcId::new(Uid::Instance(0xdef456, None), None),
1543 None,
1544 );
1545 assert_eq!(
1546 format!("{:?}", aid_no_labels),
1547 format!(
1548 "<<{}>.<{}>>",
1549 encode_base58_uid(0xabc123),
1550 encode_base58_uid(0xdef456)
1551 )
1552 );
1553 }
1554
1555 #[test]
1556 fn test_actor_id_fromstr_roundtrip() {
1557 let aid = ActorId::new(
1558 Uid::Instance(0xabc123, None),
1559 ProcId::new(
1560 Uid::Instance(0xdef456, None),
1561 Some(Label::new("my-proc").unwrap()),
1562 ),
1563 Some(Label::new("my-actor").unwrap()),
1564 );
1565 let s = aid.to_string();
1566 let parsed: ActorId = s.parse().unwrap();
1567 assert_eq!(aid, parsed);
1568 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-actor"));
1569 assert_eq!(
1570 parsed.proc_id().label().map(|l| l.as_str()),
1571 Some("my-proc")
1572 );
1573 }
1574
1575 #[test]
1576 fn test_actor_id_fromstr_with_singletons() {
1577 let parsed: ActorId = "my-actor.my-proc".parse().unwrap();
1578 assert_eq!(
1579 *parsed.uid(),
1580 Uid::singleton(Label::new("my-actor").unwrap())
1581 );
1582 assert_eq!(
1583 *parsed.proc_id().uid(),
1584 Uid::singleton(Label::new("my-proc").unwrap())
1585 );
1586 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-actor"));
1587 assert_eq!(
1588 parsed.proc_id().label().map(|l| l.as_str()),
1589 Some("my-proc")
1590 );
1591 }
1592
1593 #[test]
1594 fn test_actor_id_fromstr_mixed_examples() {
1595 let proc_uid = Uid::Instance(0xabc123, None);
1596 let parsed: ActorId = format!("controller.some-proc-123{}", proc_uid)
1597 .parse()
1598 .unwrap();
1599 assert_eq!(
1600 parsed.uid(),
1601 &Uid::singleton(Label::new("controller").unwrap())
1602 );
1603 assert_eq!(parsed.proc_id().uid(), &proc_uid);
1604 assert_eq!(
1605 parsed.label().map(|label| label.as_str()),
1606 Some("controller")
1607 );
1608 assert_eq!(
1609 parsed.proc_id().label().map(|label| label.as_str()),
1610 Some("some-proc-123")
1611 );
1612
1613 let expected_actor_uid = Uid::Instance(0xabc123, None);
1614 let expected_proc_uid = Uid::Instance(0xdef456, None);
1615 let parsed: ActorId = format!("{}.{}", expected_actor_uid, expected_proc_uid)
1616 .parse()
1617 .unwrap();
1618 assert_eq!(parsed.uid(), &expected_actor_uid);
1619 assert_eq!(parsed.proc_id().uid(), &expected_proc_uid);
1620 assert_eq!(parsed.label(), None);
1621 assert_eq!(parsed.proc_id().label(), None);
1622
1623 let expected_actor_uid = Uid::Instance(0xabc123, None);
1624 let parsed: ActorId = format!("controller{}.local", expected_actor_uid)
1625 .parse()
1626 .unwrap();
1627 assert_eq!(parsed.uid(), &expected_actor_uid);
1628 assert_eq!(
1629 parsed.proc_id().uid(),
1630 &Uid::singleton(Label::new("local").unwrap())
1631 );
1632 assert_eq!(
1633 parsed.label().map(|label| label.as_str()),
1634 Some("controller")
1635 );
1636 assert_eq!(
1637 parsed.proc_id().label().map(|label| label.as_str()),
1638 Some("local")
1639 );
1640 }
1641
1642 #[test]
1643 fn test_actor_id_fromstr_errors() {
1644 assert!("no-dot-here".parse::<ActorId>().is_err());
1645 assert!(".".parse::<ActorId>().is_err());
1646 assert!("abc.".parse::<ActorId>().is_err());
1647 assert!(".abc".parse::<ActorId>().is_err());
1648 }
1649
1650 #[test]
1651 fn test_actor_id_fromstr_errors_are_stable() {
1652 assert_eq!(
1653 "local".parse::<ActorId>().unwrap_err().to_string(),
1654 "invalid actor id: expected format `<actor>.<proc>`"
1655 );
1656 assert_eq!(
1657 ".local".parse::<ActorId>().unwrap_err().to_string(),
1658 "invalid actor uid: invalid label: label must not be empty"
1659 );
1660 assert_eq!(
1661 "local.".parse::<ActorId>().unwrap_err().to_string(),
1662 "invalid proc uid in actor id: invalid label: label must not be empty"
1663 );
1664 assert_eq!(
1665 "local.<bad!>".parse::<ActorId>().unwrap_err().to_string(),
1666 "invalid proc uid in actor id: invalid base58 uid: bad!"
1667 );
1668 }
1669
1670 #[test]
1671 fn test_actor_id_serde_roundtrip() {
1672 let aid = ActorId::new(
1673 Uid::Instance(0xabcdef, None),
1674 ProcId::new(
1675 Uid::Instance(0x123456, None),
1676 Some(Label::new("my-proc").unwrap()),
1677 ),
1678 Some(Label::new("my-actor").unwrap()),
1679 );
1680 let json = serde_json::to_string(&aid).unwrap();
1681 let parsed: ActorId = serde_json::from_str(&json).unwrap();
1682 assert_eq!(aid, parsed);
1683 assert_eq!(parsed.label().map(|l| l.as_str()), Some("my-actor"));
1684 assert_eq!(
1685 parsed.proc_id().label().map(|l| l.as_str()),
1686 Some("my-proc")
1687 );
1688 }
1689
1690 #[test]
1691 fn test_port_id_construction_and_accessors() {
1692 let actor_uid = Uid::Instance(0xabc, None);
1693 let proc_id = ProcId::new(
1694 Uid::Instance(0xdef, None),
1695 Some(Label::new("my-proc").unwrap()),
1696 );
1697 let actor_id = ActorId::new(
1698 actor_uid,
1699 proc_id.clone(),
1700 Some(Label::new("my-actor").unwrap()),
1701 );
1702 let port = Port::from(42);
1703 let pid = PortId::new(actor_id.clone(), port);
1704 assert_eq!(pid.actor_id(), &actor_id);
1705 assert_eq!(pid.port(), port);
1706 assert_eq!(pid.proc_id(), &proc_id);
1707 }
1708
1709 #[test]
1710 fn test_port_id_eq() {
1711 let actor_id = ActorId::new(
1712 Uid::Instance(0x42, None),
1713 ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap())),
1714 Some(Label::new("actor").unwrap()),
1715 );
1716 let a = PortId::new(actor_id.clone(), Port::from(10));
1717 let b = PortId::new(actor_id, Port::from(10));
1718 assert_eq!(a, b);
1719 }
1720
1721 #[test]
1722 fn test_port_id_neq_different_port() {
1723 let actor_id = ActorId::new(
1724 Uid::Instance(0x42, None),
1725 ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap())),
1726 Some(Label::new("actor").unwrap()),
1727 );
1728 let a = PortId::new(actor_id.clone(), Port::from(10));
1729 let b = PortId::new(actor_id, Port::from(20));
1730 assert_ne!(a, b);
1731 }
1732
1733 #[test]
1734 fn test_port_id_hash() {
1735 use std::collections::hash_map::DefaultHasher;
1736
1737 let actor_id = ActorId::new(
1738 Uid::Instance(0x42, None),
1739 ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap())),
1740 Some(Label::new("actor").unwrap()),
1741 );
1742 let a = PortId::new(actor_id.clone(), Port::from(10));
1743 let b = PortId::new(actor_id, Port::from(10));
1744 let hash = |pid: &PortId| {
1745 let mut h = DefaultHasher::new();
1746 pid.hash(&mut h);
1747 h.finish()
1748 };
1749 assert_eq!(hash(&a), hash(&b));
1750 }
1751
1752 #[test]
1753 fn test_port_id_ord() {
1754 let actor_id = ActorId::new(
1755 Uid::Instance(0x42, None),
1756 ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap())),
1757 Some(Label::new("actor").unwrap()),
1758 );
1759 let a = PortId::new(actor_id.clone(), Port::from(1));
1760 let b = PortId::new(actor_id, Port::from(2));
1761 assert!(a < b);
1762 }
1763
1764 #[test]
1765 fn test_port_id_ord_actor_first() {
1766 let a = PortId::new(
1767 ActorId::new(
1768 Uid::Instance(0x01, None),
1769 ProcId::new(Uid::Instance(1, None), Some(Label::new("p").unwrap())),
1770 Some(Label::new("a").unwrap()),
1771 ),
1772 Port::from(99),
1773 );
1774 let b = PortId::new(
1775 ActorId::new(
1776 Uid::Instance(0x02, None),
1777 ProcId::new(Uid::Instance(1, None), Some(Label::new("p").unwrap())),
1778 Some(Label::new("a").unwrap()),
1779 ),
1780 Port::from(1),
1781 );
1782 assert!(a < b, "actor_id should be compared first");
1783 }
1784
1785 #[test]
1786 fn test_port_id_display() {
1787 let aid = ActorId::new(
1788 Uid::Instance(0xabc123, None),
1789 ProcId::new(
1790 Uid::Instance(0xdef456, None),
1791 Some(Label::new("my-proc").unwrap()),
1792 ),
1793 Some(Label::new("my-actor").unwrap()),
1794 );
1795 let pid = PortId::new(aid, Port::from(42));
1796 assert_eq!(
1797 pid.to_string(),
1798 format!(
1799 "my-actor<{}>.my-proc<{}>:42",
1800 encode_base58_uid(0xabc123),
1801 encode_base58_uid(0xdef456)
1802 )
1803 );
1804 }
1805
1806 #[test]
1807 fn test_port_id_fromstr_examples() {
1808 let parsed: PortId = "local.local:0".parse().unwrap();
1809 assert_eq!(
1810 parsed.actor_id().uid(),
1811 &Uid::singleton(Label::new("local").unwrap())
1812 );
1813 assert_eq!(
1814 parsed.proc_id().uid(),
1815 &Uid::singleton(Label::new("local").unwrap())
1816 );
1817 assert_eq!(parsed.port(), Port::from(0));
1818
1819 let expected_actor_uid = Uid::Instance(0xabc123, None);
1820 let parsed: PortId = format!("controller{}.local:42", expected_actor_uid)
1821 .parse()
1822 .unwrap();
1823 assert_eq!(parsed.actor_id().uid(), &expected_actor_uid);
1824 assert_eq!(
1825 parsed.actor_id().label().map(|label| label.as_str()),
1826 Some("controller")
1827 );
1828 assert_eq!(
1829 parsed.proc_id().uid(),
1830 &Uid::singleton(Label::new("local").unwrap())
1831 );
1832 assert_eq!(parsed.port(), Port::from(42));
1833
1834 let expected_actor_uid = Uid::Instance(0xabc123, None);
1835 let expected_proc_uid = Uid::Instance(0xdef456, None);
1836 let parsed: PortId = format!("{}.{}:7", expected_actor_uid, expected_proc_uid)
1837 .parse()
1838 .unwrap();
1839 assert_eq!(parsed.actor_id().uid(), &expected_actor_uid);
1840 assert_eq!(parsed.proc_id().uid(), &expected_proc_uid);
1841 assert_eq!(parsed.port(), Port::from(7));
1842 }
1843
1844 #[test]
1845 fn test_port_id_debug_all_labels() {
1846 let aid = ActorId::new(
1847 Uid::Instance(0xabc123, None),
1848 ProcId::new(
1849 Uid::Instance(0xdef456, None),
1850 Some(Label::new("my-proc").unwrap()),
1851 ),
1852 Some(Label::new("my-actor").unwrap()),
1853 );
1854 let pid = PortId::new(aid, Port::from(42));
1855 assert_eq!(
1856 format!("{:?}", pid),
1857 format!(
1858 "<'my-actor.my-proc' my-actor<{}>.my-proc<{}>:42>",
1859 encode_base58_uid(0xabc123),
1860 encode_base58_uid(0xdef456)
1861 )
1862 );
1863 }
1864
1865 #[test]
1866 fn test_port_id_debug_no_labels() {
1867 let aid = ActorId::new(
1868 Uid::Instance(0xabc123, None),
1869 ProcId::new(Uid::Instance(0xdef456, None), None),
1870 None,
1871 );
1872 let pid = PortId::new(aid, Port::from(42));
1873 assert_eq!(
1874 format!("{:?}", pid),
1875 format!(
1876 "<<{}>.<{}>:42>",
1877 encode_base58_uid(0xabc123),
1878 encode_base58_uid(0xdef456)
1879 )
1880 );
1881 }
1882
1883 #[test]
1884 fn test_port_id_debug_actor_label_only() {
1885 let aid = ActorId::new(
1886 Uid::Instance(0xabc123, None),
1887 ProcId::new(Uid::Instance(0xdef456, None), None),
1888 Some(Label::new("my-actor").unwrap()),
1889 );
1890 let pid = PortId::new(aid, Port::from(42));
1891 assert_eq!(
1892 format!("{:?}", pid),
1893 format!(
1894 "<'my-actor' my-actor<{}>.<{}>:42>",
1895 encode_base58_uid(0xabc123),
1896 encode_base58_uid(0xdef456)
1897 )
1898 );
1899 }
1900
1901 #[test]
1902 fn test_port_id_debug_proc_label_only() {
1903 let aid = ActorId::new(
1904 Uid::Instance(0xabc123, None),
1905 ProcId::new(
1906 Uid::Instance(0xdef456, None),
1907 Some(Label::new("my-proc").unwrap()),
1908 ),
1909 None,
1910 );
1911 let pid = PortId::new(aid, Port::from(42));
1912 assert_eq!(
1913 format!("{:?}", pid),
1914 format!(
1915 "<'.my-proc' <{}>.my-proc<{}>:42>",
1916 encode_base58_uid(0xabc123),
1917 encode_base58_uid(0xdef456)
1918 )
1919 );
1920 }
1921
1922 #[test]
1923 fn test_port_id_fromstr_roundtrip() {
1924 let aid = ActorId::new(
1925 Uid::Instance(0xabc123, None),
1926 ProcId::new(
1927 Uid::Instance(0xdef456, None),
1928 Some(Label::new("my-proc").unwrap()),
1929 ),
1930 Some(Label::new("my-actor").unwrap()),
1931 );
1932 let pid = PortId::new(aid, Port::from(42));
1933 let s = pid.to_string();
1934 let parsed: PortId = s.parse().unwrap();
1935 assert_eq!(pid, parsed);
1936 assert_eq!(
1937 parsed.actor_id().label().map(|l| l.as_str()),
1938 Some("my-actor")
1939 );
1940 assert_eq!(
1941 parsed.actor_id().proc_id().label().map(|l| l.as_str()),
1942 Some("my-proc")
1943 );
1944 }
1945
1946 #[test]
1947 fn test_port_id_fromstr_errors_are_stable() {
1948 assert_eq!(
1949 "local.local".parse::<PortId>().unwrap_err().to_string(),
1950 "invalid port id: expected format `<actor>:<port>`"
1951 );
1952 assert_eq!(
1953 "local.local:".parse::<PortId>().unwrap_err().to_string(),
1954 "invalid port: "
1955 );
1956 assert_eq!(
1957 "local.local:not-a-port"
1958 .parse::<PortId>()
1959 .unwrap_err()
1960 .to_string(),
1961 "invalid port: not-a-port"
1962 );
1963 assert_eq!(
1964 "local.local:7@tcp://127.0.0.1:1"
1965 .parse::<PortId>()
1966 .unwrap_err()
1967 .to_string(),
1968 "invalid port: 7@tcp://127.0.0.1:1"
1969 );
1970 }
1971
1972 #[test]
1973 fn test_port_id_fromstr_errors() {
1974 assert!("<abc>.<def>".parse::<PortId>().is_err());
1976 assert!("actor.proc:notanumber".parse::<PortId>().is_err());
1978 }
1979
1980 #[test]
1981 fn test_port_id_serde_roundtrip() {
1982 let aid = ActorId::new(
1983 Uid::Instance(0xabcdef, None),
1984 ProcId::new(
1985 Uid::Instance(0x123456, None),
1986 Some(Label::new("my-proc").unwrap()),
1987 ),
1988 Some(Label::new("my-actor").unwrap()),
1989 );
1990 let pid = PortId::new(aid, Port::from(42));
1991 let json = serde_json::to_string(&pid).unwrap();
1992 let parsed: PortId = serde_json::from_str(&json).unwrap();
1993 assert_eq!(pid, parsed);
1994 }
1995}