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