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