Skip to main content

hyperactor/
id.rs

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