Skip to main content

hyperactor/
addr.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//! References: identifiers paired with a network location.
10//!
11//! Concrete grammar:
12//!
13//! ```text
14//! location     := zmq URL understood by [`ChannelAddr::from_zmq_url`]
15//! proc-ref     := proc-id "@" location
16//! actor-ref    := actor-id "@" location
17//! port-ref     := port-id "@" location
18//!
19//! proc-id      := label | "<" uid58 ">" | label "<" uid58 ">"
20//! actor-id     := actor-part "." proc-id
21//! actor-part   := label | "<" uid58 ">" | label "<" uid58 ">"
22//! ```
23//!
24//! Examples:
25//!
26//! ```text
27//! local@inproc://0
28//! controller<2MuAHeDjLCEd>@tcp://[::1]:2345
29//! controller<2MuAHeDjLCEd>.local@inproc://0
30//! <2MuAHeDjLCEd>.<NRjEZGYjYibf>:42@tcp://[::1]:2345
31//! <2MuAHeDjLCEd>.<NRjEZGYjYibf>:handler<NRjEZGYjYibf>@tcp://[::1]:2345
32//! <2MuAHeDjLCEd>.<NRjEZGYjYibf>!introspect@tcp://[::1]:2345
33//! ```
34
35use std::fmt;
36use std::str::FromStr;
37
38use enum_as_inner::EnumAsInner;
39use serde::Deserialize;
40use serde::Serialize;
41
42use crate::channel::ChannelAddr;
43use crate::context::MailboxExt;
44use crate::id;
45use crate::id::ActorId;
46use crate::id::IdParseError;
47use crate::id::Label;
48use crate::id::PortId;
49use crate::id::ProcId;
50use crate::id::Uid;
51use crate::introspect::IntrospectMessage;
52use crate::parse;
53use crate::port::ControlPort;
54use crate::port::Port;
55use crate::proc::StatusMessage;
56use crate::ref_::PortRef;
57
58/// A network location.
59///
60/// Two variants: a terminal [`ChannelAddr`], or a "via" hop carrying
61/// the [`Uid`] of a gateway through which the inner location is
62/// reachable. The via form is a source route — a message addressed to
63/// `Via(uid, inner)` is forwarded by the gateway holding that `uid` and
64/// then routed by the inner location.
65///
66/// Display syntax:
67///
68/// ```text
69/// location := via* zmq-url
70/// via      := uid "."
71/// uid      := label | "<" base58 ">" | label "<" base58 ">"
72/// ```
73///
74/// The parser sniffs the ZMQ URL scheme (`<scheme>://`) to split the
75/// via list from the URL — so any uid form accepted by [`Uid`]
76/// (singleton label, unlabeled instance, or labeled instance) is
77/// admitted in via position.
78///
79/// Examples:
80///
81/// * `<2MuAHeDjLCEd>.tcp://[::1]:2345` — one unlabeled instance via.
82/// * `host<7PDmJtQJB5S>.tcp://[::1]:2345` — labeled instance via.
83/// * `client.host<7PDmJtQJB5S>.tcp://[::1]:2345` — a singleton via
84///   stacked on a labeled instance via.
85#[derive(
86    Clone,
87    EnumAsInner,
88    PartialEq,
89    Eq,
90    Hash,
91    PartialOrd,
92    Ord,
93    Serialize,
94    Deserialize
95)]
96pub enum Location {
97    /// A terminal channel address. Routed directly.
98    Addr(ChannelAddr),
99    /// A via hop: messages for this location are forwarded by the
100    /// gateway holding `Uid`, which peels this prefix off the
101    /// destination before routing the inner location.
102    Via(Uid, Box<Location>),
103}
104
105impl Location {
106    /// Returns the innermost channel address, peeling all via hops.
107    pub fn addr(&self) -> &ChannelAddr {
108        match self {
109            Location::Addr(addr) => addr,
110            Location::Via(_, inner) => inner.addr(),
111        }
112    }
113
114    /// Wrap this location in a via hop carrying `uid`. Vias act like a
115    /// stack: the outermost (most recently added) hop is the first the
116    /// receiving gateway peels off.
117    pub fn with_via(self, uid: Uid) -> Self {
118        Location::Via(uid, Box::new(self))
119    }
120
121    /// Pop the outermost via hop. Returns `Ok((uid, inner))` when this
122    /// is a `Via`, or `Err(self)` when it is a terminal `Addr`, so the
123    /// caller can recover the unchanged location.
124    pub fn pop_via(self) -> Result<(Uid, Location), Location> {
125        match self {
126            Location::Via(uid, inner) => Ok((uid, *inner)),
127            addr @ Location::Addr(_) => Err(addr),
128        }
129    }
130}
131
132impl From<ChannelAddr> for Location {
133    fn from(addr: ChannelAddr) -> Self {
134        Location::Addr(addr)
135    }
136}
137
138impl fmt::Display for Location {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            Location::Addr(addr) => f.write_str(&addr.to_zmq_url()),
142            Location::Via(uid, inner) => write!(f, "{uid}.{inner}"),
143        }
144    }
145}
146
147impl fmt::Debug for Location {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        fmt::Display::fmt(self, f)
150    }
151}
152
153impl FromStr for Location {
154    type Err = anyhow::Error;
155
156    fn from_str(s: &str) -> Result<Self, Self::Err> {
157        parse::addr::parse_location_str(s).map_err(|err| anyhow::anyhow!("{err}"))
158    }
159}
160
161/// Errors that can occur when parsing a [`ProcAddr`] or [`ActorAddr`].
162#[derive(Debug, thiserror::Error)]
163pub enum AddrParseError {
164    /// The `@` separator between id and location is missing.
165    #[error("missing '@' separator between id and location")]
166    MissingSeparator,
167    /// The id portion is invalid.
168    #[error("invalid id: {0}")]
169    InvalidId(#[from] IdParseError),
170    /// The location portion is invalid.
171    #[error("invalid location: {0}")]
172    InvalidLocation(#[source] anyhow::Error),
173}
174
175/// A process identifier paired with a network location.
176#[derive(Clone, Serialize, Deserialize, typeuri::Named)]
177pub struct ProcAddr {
178    id: ProcId,
179    location: Location,
180}
181
182impl ProcAddr {
183    /// Create a new [`ProcAddr`].
184    pub fn new(id: ProcId, location: Location) -> Self {
185        Self { id, location }
186    }
187
188    /// Returns the process id.
189    pub fn id(&self) -> &ProcId {
190        &self.id
191    }
192
193    /// Returns the location.
194    pub fn location(&self) -> &Location {
195        &self.location
196    }
197
198    /// The proc's channel address.
199    pub fn addr(&self) -> &ChannelAddr {
200        self.location.addr()
201    }
202
203    /// The proc's uid.
204    pub fn uid(&self) -> &Uid {
205        self.id.uid()
206    }
207
208    /// The proc's label: the explicit metadata label for instances,
209    /// or the singleton name for singletons.
210    pub fn label(&self) -> Option<&Label> {
211        self.id.label()
212    }
213
214    /// Create a ProcAddr with an anonymous instance proc id.
215    pub fn anonymous(addr: ChannelAddr) -> Self {
216        Self::new(id::ProcId::anonymous(), Location::from(addr))
217    }
218
219    /// Create a ProcAddr with an instance proc id and the given display label.
220    pub fn instance(addr: ChannelAddr, base_name: impl AsRef<str>) -> Self {
221        let label = Label::strip(base_name.as_ref());
222        Self::new(id::ProcId::instance(label), Location::from(addr))
223    }
224
225    /// Create a ProcAddr with a singleton proc id identified by the given name.
226    pub fn singleton(addr: ChannelAddr, name: impl AsRef<str>) -> Self {
227        Self::new(
228            id::ProcId::singleton(Label::strip(name.as_ref())),
229            Location::from(addr),
230        )
231    }
232
233    /// Create an ActorAddr singleton with the provided name within this proc.
234    pub fn actor_addr(&self, name: impl AsRef<str>) -> ActorAddr {
235        let uid = Uid::singleton(Label::strip(name.as_ref()));
236        self.actor_addr_uid(uid)
237    }
238
239    /// Create an ActorAddr with the provided uid within this proc.
240    pub fn actor_addr_uid(&self, uid: Uid) -> ActorAddr {
241        ActorAddr::new_from_uid(self.clone(), uid)
242    }
243
244    /// A human-readable name for logging.
245    pub fn log_name(&self) -> &str {
246        self.label().map(|l| l.as_str()).unwrap_or("?")
247    }
248}
249
250impl PartialEq for ProcAddr {
251    fn eq(&self, other: &Self) -> bool {
252        self.id == other.id && self.location == other.location
253    }
254}
255
256impl Eq for ProcAddr {}
257
258impl std::hash::Hash for ProcAddr {
259    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
260        self.id.hash(state);
261        self.location.hash(state);
262    }
263}
264
265impl PartialOrd for ProcAddr {
266    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
267        Some(self.cmp(other))
268    }
269}
270
271impl Ord for ProcAddr {
272    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
273        self.id
274            .cmp(&other.id)
275            .then_with(|| self.location.cmp(&other.location))
276    }
277}
278
279impl fmt::Display for ProcAddr {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        write!(f, "{}@{}", self.id, self.location)
282    }
283}
284
285impl fmt::Debug for ProcAddr {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        match self.id.label() {
288            Some(label) => write!(f, "<'{}' {}@{}>", label, self.id, self.location),
289            None => write!(f, "<{}@{}>", self.id, self.location),
290        }
291    }
292}
293
294impl FromStr for ProcAddr {
295    type Err = AddrParseError;
296
297    fn from_str(s: &str) -> Result<Self, Self::Err> {
298        parse::addr::parse_proc_addr(s).map_err(|_| legacy_parse_proc_ref(s))
299    }
300}
301
302/// An actor identifier paired with a network location.
303#[derive(Clone, Serialize, Deserialize, typeuri::Named)]
304pub struct ActorAddr {
305    id: ActorId,
306    location: Location,
307}
308
309hyperactor_config::impl_attrvalue!(ActorAddr);
310
311impl ActorAddr {
312    /// Create a new [`ActorAddr`].
313    pub fn new(id: ActorId, location: Location) -> Self {
314        Self { id, location }
315    }
316
317    /// Create an ActorAddr from a ProcAddr and actor uid.
318    pub fn new_from_uid(proc_ref: ProcAddr, uid: Uid) -> Self {
319        let actor_id = id::ActorId::new(uid, proc_ref.id.clone(), None);
320        Self::new(actor_id, proc_ref.location)
321    }
322
323    /// Returns the actor id.
324    pub fn id(&self) -> &ActorId {
325        &self.id
326    }
327
328    /// Returns the proc id that owns this actor id.
329    pub fn proc_id(&self) -> &ProcId {
330        self.id.proc_id()
331    }
332
333    /// Returns the location.
334    pub fn location(&self) -> &Location {
335        &self.location
336    }
337
338    /// The actor's channel address.
339    pub fn addr(&self) -> &ChannelAddr {
340        self.location.addr()
341    }
342
343    /// The actor's uid.
344    pub fn uid(&self) -> &Uid {
345        self.id.uid()
346    }
347
348    /// The actor's label: explicit metadata label for instances,
349    /// or singleton name for singletons.
350    pub fn label(&self) -> Option<&Label> {
351        self.id.label()
352    }
353
354    /// Reconstruct the parent ProcAddr (with location preserved).
355    pub fn proc_addr(&self) -> ProcAddr {
356        ProcAddr::new(self.id.proc_id().clone(), self.location.clone())
357    }
358
359    /// Create a PortAddr for a port on this actor.
360    pub fn port_addr(&self, port: Port) -> PortAddr {
361        PortAddr::new(
362            id::PortId::new(self.id.clone(), port),
363            self.location.clone(),
364        )
365    }
366
367    /// The actor's introspection control port.
368    pub fn introspect_port(&self) -> PortRef<IntrospectMessage> {
369        PortRef::attest(self.port_addr(Port::control(ControlPort::Introspect)))
370    }
371
372    /// The actor's lifecycle status control port.
373    pub fn status_port(&self) -> PortRef<StatusMessage> {
374        PortRef::attest(self.port_addr(Port::control(ControlPort::Status)))
375    }
376
377    /// Create an ActorAddr for a root actor on a proc.
378    pub fn root(proc_ref: ProcAddr, label: impl Into<Label>) -> Self {
379        let label = label.into();
380        let actor_id = id::ActorId::singleton(label, proc_ref.id.clone());
381        Self::new(actor_id, proc_ref.location)
382    }
383
384    /// Create an ActorAddr for a child actor with a random uid.
385    pub fn anonymous_child(&self) -> Self {
386        let child_id = id::ActorId::anonymous(self.id.proc_id().clone());
387        Self::new(child_id, self.location.clone())
388    }
389
390    /// Whether this is a root actor (singleton uid).
391    pub fn is_root(&self) -> bool {
392        self.id.uid().is_singleton()
393    }
394
395    /// A human-readable name for logging.
396    pub fn log_name(&self) -> &str {
397        self.label().map(|l| l.as_str()).unwrap_or("?")
398    }
399}
400
401impl PartialEq for ActorAddr {
402    fn eq(&self, other: &Self) -> bool {
403        self.id == other.id && self.location == other.location
404    }
405}
406
407impl Eq for ActorAddr {}
408
409impl std::hash::Hash for ActorAddr {
410    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
411        self.id.hash(state);
412        self.location.hash(state);
413    }
414}
415
416impl PartialOrd for ActorAddr {
417    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
418        Some(self.cmp(other))
419    }
420}
421
422impl Ord for ActorAddr {
423    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
424        self.id
425            .cmp(&other.id)
426            .then_with(|| self.location.cmp(&other.location))
427    }
428}
429
430impl fmt::Display for ActorAddr {
431    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432        write!(f, "{}@{}", self.id, self.location)
433    }
434}
435
436impl fmt::Debug for ActorAddr {
437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438        match (self.id.label(), self.id.proc_id().label()) {
439            (Some(actor_label), Some(proc_label)) => {
440                write!(
441                    f,
442                    "<'{}.{}' {}@{}>",
443                    actor_label, proc_label, self.id, self.location
444                )
445            }
446            (Some(actor_label), None) => {
447                write!(f, "<'{}' {}@{}>", actor_label, self.id, self.location)
448            }
449            (None, Some(proc_label)) => {
450                write!(f, "<'.{}' {}@{}>", proc_label, self.id, self.location)
451            }
452            (None, None) => {
453                write!(f, "<{}@{}>", self.id, self.location)
454            }
455        }
456    }
457}
458
459impl FromStr for ActorAddr {
460    type Err = AddrParseError;
461
462    fn from_str(s: &str) -> Result<Self, Self::Err> {
463        parse::addr::parse_actor_addr(s).map_err(|_| legacy_parse_actor_ref(s))
464    }
465}
466
467/// A port identifier paired with a network location.
468#[derive(Clone, Serialize, Deserialize, typeuri::Named)]
469pub struct PortAddr {
470    id: PortId,
471    location: Location,
472}
473
474impl PortAddr {
475    /// Create a new [`PortAddr`].
476    pub fn new(id: PortId, location: Location) -> Self {
477        Self { id, location }
478    }
479
480    /// Returns the port id.
481    pub fn id(&self) -> &PortId {
482        &self.id
483    }
484
485    /// Returns the location.
486    pub fn location(&self) -> &Location {
487        &self.location
488    }
489
490    /// Returns the actor id (delegates to port id).
491    pub fn actor_id(&self) -> &ActorId {
492        self.id.actor_id()
493    }
494
495    /// Returns the proc id that owns this port id.
496    pub fn proc_id(&self) -> &ProcId {
497        self.id.actor_id().proc_id()
498    }
499
500    /// Whether this is a handler port.
501    pub(crate) fn is_handler_port(&self) -> bool {
502        self.id.port().is_handler()
503    }
504
505    /// Whether this is the provided control port.
506    pub(crate) fn is_control_port_kind(&self, port: ControlPort) -> bool {
507        self.id.port() == Port::control(port)
508    }
509
510    /// The port.
511    pub fn port(&self) -> Port {
512        self.id.port()
513    }
514
515    /// The port index.
516    pub fn index(&self) -> u64 {
517        self.id.port().as_u64()
518    }
519
520    /// The ephemeral port index.
521    pub fn ephemeral_index(&self) -> Option<u64> {
522        self.id.port().ephemeral_index()
523    }
524
525    /// Reconstruct the parent ActorAddr (with location preserved).
526    pub fn actor_addr(&self) -> ActorAddr {
527        ActorAddr::new(self.id.actor_id().clone(), self.location.clone())
528    }
529
530    /// Reconstruct the parent ActorAddr (with location preserved).
531    pub fn actor_ref(&self) -> ActorAddr {
532        self.actor_addr()
533    }
534
535    /// Send a serialized message to this port, provided a sending capability.
536    pub fn send(&self, cx: &impl crate::context::Actor, serialized: wirevalue::Any) {
537        let mut headers = hyperactor_config::Flattrs::new();
538        crate::mailbox::headers::set_send_timestamp(&mut headers);
539        cx.post(
540            self.clone(),
541            headers,
542            serialized,
543            true,
544            crate::context::SeqInfoPolicy::AssignNew,
545        );
546    }
547
548    /// Send a serialized message with explicit headers.
549    pub fn send_with_headers(
550        &self,
551        cx: &impl crate::context::Actor,
552        serialized: wirevalue::Any,
553        mut headers: hyperactor_config::Flattrs,
554    ) {
555        crate::mailbox::headers::set_send_timestamp(&mut headers);
556        cx.post(
557            self.clone(),
558            headers,
559            serialized,
560            true,
561            crate::context::SeqInfoPolicy::AssignNew,
562        );
563    }
564
565    /// Split this port through a local proxy, possibly reducing messages.
566    pub fn split(
567        &self,
568        cx: &impl crate::context::Actor,
569        reducer_spec: Option<crate::accum::ReducerSpec>,
570        reducer_mode: crate::accum::ReducerMode,
571        return_undeliverable: bool,
572    ) -> anyhow::Result<PortAddr> {
573        cx.split(
574            self.clone(),
575            reducer_spec,
576            reducer_mode,
577            return_undeliverable,
578        )
579    }
580}
581
582impl PartialEq for PortAddr {
583    fn eq(&self, other: &Self) -> bool {
584        self.id == other.id && self.location == other.location
585    }
586}
587
588impl Eq for PortAddr {}
589
590impl std::hash::Hash for PortAddr {
591    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
592        self.id.hash(state);
593        self.location.hash(state);
594    }
595}
596
597impl PartialOrd for PortAddr {
598    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
599        Some(self.cmp(other))
600    }
601}
602
603impl Ord for PortAddr {
604    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
605        self.id
606            .cmp(&other.id)
607            .then_with(|| self.location.cmp(&other.location))
608    }
609}
610
611impl fmt::Display for PortAddr {
612    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613        write!(f, "{}@{}", self.id, self.location)
614    }
615}
616
617impl fmt::Debug for PortAddr {
618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619        match (
620            self.id.actor_id().label(),
621            self.id.actor_id().proc_id().label(),
622        ) {
623            (Some(actor_label), Some(proc_label)) => {
624                write!(
625                    f,
626                    "<'{}.{}' {}@{}>",
627                    actor_label, proc_label, self.id, self.location
628                )
629            }
630            (Some(actor_label), None) => {
631                write!(f, "<'{}' {}@{}>", actor_label, self.id, self.location)
632            }
633            (None, Some(proc_label)) => {
634                write!(f, "<'.{}' {}@{}>", proc_label, self.id, self.location)
635            }
636            (None, None) => {
637                write!(f, "<{}@{}>", self.id, self.location)
638            }
639        }
640    }
641}
642
643impl FromStr for PortAddr {
644    type Err = AddrParseError;
645
646    fn from_str(s: &str) -> Result<Self, Self::Err> {
647        parse::addr::parse_port_addr(s).map_err(|_| legacy_parse_port_ref(s))
648    }
649}
650
651/// A polymorphic reference: proc, actor, or port.
652///
653/// Used for prefix-based routing in [`MailboxRouter`] and
654/// [`DialMailboxRouter`]. Ordering is lexicographic by
655/// (proc, actor uid, port).
656#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq, Hash, Serialize, Deserialize)]
657pub enum Addr {
658    /// A process reference.
659    Proc(ProcAddr),
660    /// An actor reference.
661    Actor(ActorAddr),
662    /// A port reference.
663    Port(PortAddr),
664}
665
666impl Addr {
667    /// Whether `self` is a prefix of `other`.
668    ///
669    /// - Proc is a prefix of any Actor or Port on the same proc.
670    /// - Actor is a prefix of any Port on the same actor.
671    pub fn is_prefix_of(&self, other: &Self) -> bool {
672        match (self, other) {
673            (Self::Proc(p), Self::Actor(a)) => *p == a.proc_addr(),
674            (Self::Proc(p), Self::Port(pt)) => *p == pt.actor_addr().proc_addr(),
675            (Self::Actor(a), Self::Port(pt)) => *a == pt.actor_addr(),
676            (Self::Proc(p1), Self::Proc(p2)) => p1 == p2,
677            (Self::Actor(a1), Self::Actor(a2)) => a1 == a2,
678            (Self::Port(p1), Self::Port(p2)) => p1 == p2,
679            _ => false,
680        }
681    }
682
683    /// The proc addr of this reference.
684    pub fn proc_addr(&self) -> ProcAddr {
685        match self {
686            Self::Proc(p) => p.clone(),
687            Self::Actor(a) => a.proc_addr(),
688            Self::Port(p) => p.actor_addr().proc_addr(),
689        }
690    }
691}
692
693impl PartialOrd for Addr {
694    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
695        Some(self.cmp(other))
696    }
697}
698
699impl Ord for Addr {
700    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
701        // Order by: proc, then actor uid (None < Some), then port (None < Some).
702        let proc_ord = self.proc_addr().cmp(&other.proc_addr());
703        if proc_ord != std::cmp::Ordering::Equal {
704            return proc_ord;
705        }
706        let self_actor_uid = match self {
707            Self::Proc(_) => None,
708            Self::Actor(a) => Some(a.uid()),
709            Self::Port(p) => Some(p.actor_id().uid()),
710        };
711        let other_actor_uid = match other {
712            Self::Proc(_) => None,
713            Self::Actor(a) => Some(a.uid()),
714            Self::Port(p) => Some(p.actor_id().uid()),
715        };
716        let actor_ord = self_actor_uid.cmp(&other_actor_uid);
717        if actor_ord != std::cmp::Ordering::Equal {
718            return actor_ord;
719        }
720        let self_port = match self {
721            Self::Port(p) => Some(p.id().port()),
722            _ => None,
723        };
724        let other_port = match other {
725            Self::Port(p) => Some(p.id().port()),
726            _ => None,
727        };
728        self_port.cmp(&other_port)
729    }
730}
731
732impl fmt::Display for Addr {
733    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734        match self {
735            Self::Proc(p) => fmt::Display::fmt(p, f),
736            Self::Actor(a) => fmt::Display::fmt(a, f),
737            Self::Port(p) => fmt::Display::fmt(p, f),
738        }
739    }
740}
741
742impl FromStr for Addr {
743    type Err = AddrParseError;
744
745    fn from_str(s: &str) -> Result<Self, Self::Err> {
746        parse::addr::parse_addr(s).map_err(|_| legacy_parse_reference(s))
747    }
748}
749
750fn split_ref_input(s: &str) -> Result<(&str, &str), AddrParseError> {
751    let Some((id_text, location_text)) = s.split_once('@') else {
752        return Err(AddrParseError::MissingSeparator);
753    };
754    Ok((id_text, location_text))
755}
756
757fn legacy_parse_proc_ref(s: &str) -> AddrParseError {
758    let Ok((id_text, location_text)) = split_ref_input(s) else {
759        return AddrParseError::MissingSeparator;
760    };
761    if let Err(err) = id_text.parse::<ProcId>() {
762        return AddrParseError::InvalidId(err);
763    }
764    let location = if location_text.is_empty() {
765        "@"
766    } else {
767        location_text
768    };
769    let err = location.parse::<Location>().unwrap_err();
770    AddrParseError::InvalidLocation(err)
771}
772
773fn legacy_parse_actor_ref(s: &str) -> AddrParseError {
774    let Ok((id_text, location_text)) = split_ref_input(s) else {
775        return AddrParseError::MissingSeparator;
776    };
777    if let Err(err) = id_text.parse::<ActorId>() {
778        return AddrParseError::InvalidId(err);
779    }
780    let location = if location_text.is_empty() {
781        "@"
782    } else {
783        location_text
784    };
785    let err = location.parse::<Location>().unwrap_err();
786    AddrParseError::InvalidLocation(err)
787}
788
789fn legacy_parse_port_ref(s: &str) -> AddrParseError {
790    let Ok((id_text, location_text)) = split_ref_input(s) else {
791        return AddrParseError::MissingSeparator;
792    };
793    if let Err(err) = id_text.parse::<PortId>() {
794        return AddrParseError::InvalidId(err);
795    }
796    let location = if location_text.is_empty() {
797        "@"
798    } else {
799        location_text
800    };
801    let err = location.parse::<Location>().unwrap_err();
802    AddrParseError::InvalidLocation(err)
803}
804
805fn legacy_parse_reference(s: &str) -> AddrParseError {
806    let Ok((id_text, location_text)) = split_ref_input(s) else {
807        return AddrParseError::MissingSeparator;
808    };
809    let location = if location_text.is_empty() {
810        "@"
811    } else {
812        location_text
813    };
814    let location_err = || {
815        let err = location.parse::<Location>().unwrap_err();
816        AddrParseError::InvalidLocation(err)
817    };
818
819    let port_result = id_text.parse::<PortId>();
820    if port_result.is_ok() {
821        return location_err();
822    }
823    let actor_result = id_text.parse::<ActorId>();
824    if actor_result.is_ok() {
825        return location_err();
826    }
827    let proc_result = id_text.parse::<ProcId>();
828    if proc_result.is_ok() {
829        return location_err();
830    }
831
832    if id_text.contains(':') {
833        return AddrParseError::InvalidId(port_result.unwrap_err());
834    }
835    if id_text.contains('.') {
836        return AddrParseError::InvalidId(actor_result.unwrap_err());
837    }
838
839    AddrParseError::InvalidId(proc_result.unwrap_err())
840}
841
842impl From<ProcAddr> for Addr {
843    fn from(p: ProcAddr) -> Self {
844        Self::Proc(p)
845    }
846}
847
848impl From<ActorAddr> for Addr {
849    fn from(a: ActorAddr) -> Self {
850        Self::Actor(a)
851    }
852}
853
854impl From<PortAddr> for Addr {
855    fn from(p: PortAddr) -> Self {
856        Self::Port(p)
857    }
858}
859
860#[cfg(test)]
861mod tests {
862    use std::hash::Hash;
863
864    use super::*;
865    use crate::id::Label;
866    use crate::id::Uid;
867    use crate::port::Port;
868
869    #[test]
870    fn test_location_display_fromstr_roundtrip() {
871        let loc: Location = ChannelAddr::Local(42).into();
872        let s = loc.to_string();
873        assert_eq!(s, "inproc://42");
874        let parsed: Location = s.parse().unwrap();
875        assert_eq!(loc, parsed);
876    }
877
878    #[test]
879    fn test_location_tcp() {
880        let addr: ChannelAddr = "tcp:127.0.0.1:8080".parse().unwrap();
881        let loc = Location::from(addr.clone());
882        assert_eq!(loc.to_string(), "tcp://127.0.0.1:8080");
883        assert_eq!(loc.addr(), &addr);
884    }
885
886    #[test]
887    fn test_location_debug_same_as_display() {
888        let loc: Location = ChannelAddr::Local(7).into();
889        assert_eq!(format!("{:?}", loc), format!("{}", loc));
890    }
891
892    #[test]
893    fn test_proc_ref_display() {
894        let pid = ProcId::new(
895            Uid::Instance(0xabc123, None),
896            Some(Label::new("my-proc").unwrap()),
897        );
898        let loc: Location = ChannelAddr::Local(42).into();
899        let pref = ProcAddr::new(pid, loc);
900        assert_eq!(pref.to_string(), format!("{}@inproc://42", pref.id()));
901    }
902
903    #[test]
904    fn test_proc_addr_identity_constructors() {
905        let anonymous = ProcAddr::anonymous(ChannelAddr::Local(1));
906        assert!(
907            matches!(anonymous.id().uid(), Uid::Instance(_, None)),
908            "anonymous proc addr must have an unlabeled instance id"
909        );
910        assert_eq!(anonymous.label(), None);
911        assert_eq!(*anonymous.location().addr(), ChannelAddr::Local(1));
912
913        let instance = ProcAddr::instance(ChannelAddr::Local(2), "worker");
914        assert!(
915            matches!(
916                instance.id().uid(),
917                Uid::Instance(_, Some(label)) if label.as_str() == "worker"
918            ),
919            "instance proc addr must have a labeled instance id"
920        );
921        assert_eq!(instance.label().map(|label| label.as_str()), Some("worker"));
922        assert_eq!(*instance.location().addr(), ChannelAddr::Local(2));
923
924        let singleton = ProcAddr::singleton(ChannelAddr::Local(3), "controller");
925        assert!(
926            matches!(
927                singleton.id().uid(),
928                Uid::Singleton(label) if label.as_str() == "controller"
929            ),
930            "singleton proc addr must have a singleton id"
931        );
932        assert_eq!(
933            singleton.label().map(|label| label.as_str()),
934            Some("controller")
935        );
936        assert_eq!(*singleton.location().addr(), ChannelAddr::Local(3));
937    }
938
939    #[test]
940    fn test_proc_ref_debug_with_label() {
941        let pid = ProcId::new(
942            Uid::Instance(0xabc123, None),
943            Some(Label::new("my-proc").unwrap()),
944        );
945        let loc: Location = ChannelAddr::Local(42).into();
946        let pref = ProcAddr::new(pid, loc);
947        assert_eq!(
948            format!("{:?}", pref),
949            format!("<'my-proc' {}@inproc://42>", pref.id())
950        );
951    }
952
953    #[test]
954    fn test_proc_ref_debug_without_label() {
955        let pid = ProcId::new(Uid::Instance(0xabc123, None), None);
956        let loc: Location = ChannelAddr::Local(42).into();
957        let pref = ProcAddr::new(pid, loc);
958        assert_eq!(
959            format!("{:?}", pref),
960            format!("<{}@inproc://42>", pref.id())
961        );
962    }
963
964    #[test]
965    fn test_proc_ref_fromstr_roundtrip() {
966        let pid = ProcId::new(
967            Uid::Instance(0xabc123, None),
968            Some(Label::new("my-proc").unwrap()),
969        );
970        let loc: Location = ChannelAddr::Local(42).into();
971        let pref = ProcAddr::new(pid, loc);
972        let s = pref.to_string();
973        let parsed: ProcAddr = s.parse().unwrap();
974        assert_eq!(pref, parsed);
975    }
976
977    #[test]
978    fn test_proc_ref_fromstr_tcp() {
979        let parsed: ProcAddr = format!(
980            "{}@tcp://127.0.0.1:8080",
981            ProcId::new(Uid::Instance(0xabc123, None), None)
982        )
983        .parse()
984        .unwrap();
985        assert_eq!(*parsed.id().uid(), Uid::Instance(0xabc123, None));
986        assert_eq!(
987            *parsed.location().addr(),
988            "tcp:127.0.0.1:8080".parse::<ChannelAddr>().unwrap()
989        );
990    }
991
992    #[test]
993    fn test_proc_ref_fromstr_examples() {
994        let parsed: ProcAddr = "local@inproc://0".parse().unwrap();
995        assert_eq!(
996            parsed.id().uid(),
997            &Uid::singleton(Label::new("local").unwrap())
998        );
999        assert_eq!(*parsed.location().addr(), ChannelAddr::Local(0));
1000
1001        let expected_uid = Uid::Instance(0xabc123, None);
1002        let parsed: ProcAddr = format!("controller{}@tcp://[::1]:2345", expected_uid)
1003            .parse()
1004            .unwrap();
1005        assert_eq!(parsed.id().uid(), &expected_uid);
1006        assert_eq!(
1007            parsed.id().label().map(|label| label.as_str()),
1008            Some("controller")
1009        );
1010        assert_eq!(
1011            *parsed.location().addr(),
1012            "tcp:[::1]:2345".parse::<ChannelAddr>().unwrap()
1013        );
1014    }
1015
1016    #[test]
1017    fn test_proc_ref_fromstr_missing_separator() {
1018        let err = ProcId::new(Uid::Instance(0xabc123, None), None)
1019            .to_string()
1020            .parse::<ProcAddr>()
1021            .unwrap_err();
1022        assert!(matches!(err, AddrParseError::MissingSeparator));
1023    }
1024
1025    #[test]
1026    fn test_proc_ref_fromstr_invalid_location() {
1027        let err = "local@tcp://".parse::<ProcAddr>().unwrap_err();
1028        assert!(matches!(err, AddrParseError::InvalidLocation(_)));
1029    }
1030
1031    #[test]
1032    fn test_actor_ref_display() {
1033        let aid = ActorId::new(
1034            Uid::Instance(0xabc123, None),
1035            ProcId::new(
1036                Uid::Instance(0xdef456, None),
1037                Some(Label::new("my-proc").unwrap()),
1038            ),
1039            Some(Label::new("my-actor").unwrap()),
1040        );
1041        let loc: Location = ChannelAddr::Local(42).into();
1042        let aref = ActorAddr::new(aid, loc);
1043        assert_eq!(aref.to_string(), format!("{}@inproc://42", aref.id()));
1044    }
1045
1046    #[test]
1047    fn test_actor_and_port_proc_id_accessors() {
1048        let proc_id = ProcId::new(
1049            Uid::Instance(0xdef456, None),
1050            Some(Label::new("my-proc").unwrap()),
1051        );
1052        let actor_id = ActorId::new(
1053            Uid::Instance(0xabc123, None),
1054            proc_id.clone(),
1055            Some(Label::new("my-actor").unwrap()),
1056        );
1057        let actor_addr = ActorAddr::new(actor_id, ChannelAddr::Local(42).into());
1058        let port_addr = actor_addr.port_addr(Port::from(7));
1059
1060        assert_eq!(actor_addr.proc_id(), &proc_id);
1061        assert_eq!(port_addr.actor_id(), actor_addr.id());
1062        assert_eq!(port_addr.proc_id(), &proc_id);
1063    }
1064
1065    #[test]
1066    fn test_actor_ref_debug_all_labels() {
1067        let aid = ActorId::new(
1068            Uid::Instance(0xabc123, None),
1069            ProcId::new(
1070                Uid::Instance(0xdef456, None),
1071                Some(Label::new("my-proc").unwrap()),
1072            ),
1073            Some(Label::new("my-actor").unwrap()),
1074        );
1075        let loc: Location = ChannelAddr::Local(42).into();
1076        let aref = ActorAddr::new(aid, loc);
1077        assert_eq!(
1078            format!("{:?}", aref),
1079            format!("<'my-actor.my-proc' {}@inproc://42>", aref.id())
1080        );
1081    }
1082
1083    #[test]
1084    fn test_actor_ref_debug_no_labels() {
1085        let aid = ActorId::new(
1086            Uid::Instance(0xabc123, None),
1087            ProcId::new(Uid::Instance(0xdef456, None), None),
1088            None,
1089        );
1090        let loc: Location = ChannelAddr::Local(42).into();
1091        let aref = ActorAddr::new(aid, loc);
1092        assert_eq!(
1093            format!("{:?}", aref),
1094            format!("<{}@inproc://42>", aref.id())
1095        );
1096    }
1097
1098    #[test]
1099    fn test_actor_ref_debug_actor_label_only() {
1100        let aid = ActorId::new(
1101            Uid::Instance(0xabc123, None),
1102            ProcId::new(Uid::Instance(0xdef456, None), None),
1103            Some(Label::new("my-actor").unwrap()),
1104        );
1105        let loc: Location = ChannelAddr::Local(42).into();
1106        let aref = ActorAddr::new(aid, loc);
1107        assert_eq!(
1108            format!("{:?}", aref),
1109            format!("<'my-actor' {}@inproc://42>", aref.id())
1110        );
1111    }
1112
1113    #[test]
1114    fn test_actor_ref_debug_proc_label_only() {
1115        let aid = ActorId::new(
1116            Uid::Instance(0xabc123, None),
1117            ProcId::new(
1118                Uid::Instance(0xdef456, None),
1119                Some(Label::new("my-proc").unwrap()),
1120            ),
1121            None,
1122        );
1123        let loc: Location = ChannelAddr::Local(42).into();
1124        let aref = ActorAddr::new(aid, loc);
1125        assert_eq!(
1126            format!("{:?}", aref),
1127            format!("<'.my-proc' {}@inproc://42>", aref.id())
1128        );
1129    }
1130
1131    #[test]
1132    fn test_actor_ref_fromstr_roundtrip() {
1133        let aid = ActorId::new(
1134            Uid::Instance(0xabc123, None),
1135            ProcId::new(
1136                Uid::Instance(0xdef456, None),
1137                Some(Label::new("my-proc").unwrap()),
1138            ),
1139            Some(Label::new("my-actor").unwrap()),
1140        );
1141        let loc: Location = ChannelAddr::Local(42).into();
1142        let aref = ActorAddr::new(aid, loc);
1143        let s = aref.to_string();
1144        let parsed: ActorAddr = s.parse().unwrap();
1145        assert_eq!(aref, parsed);
1146        assert_eq!(parsed.id.label().map(|l| l.as_str()), Some("my-actor"));
1147        assert_eq!(
1148            parsed.id.proc_id().label().map(|l| l.as_str()),
1149            Some("my-proc")
1150        );
1151    }
1152
1153    #[test]
1154    fn test_actor_ref_fromstr_examples() {
1155        let expected_actor_uid = Uid::Instance(0xabc123, None);
1156        let parsed: ActorAddr = format!("controller{}.local@inproc://0", expected_actor_uid)
1157            .parse()
1158            .unwrap();
1159        assert_eq!(parsed.id().uid(), &expected_actor_uid);
1160        assert_eq!(
1161            parsed.id().label().map(|label| label.as_str()),
1162            Some("controller")
1163        );
1164        assert_eq!(
1165            parsed.id().proc_id().uid(),
1166            &Uid::singleton(Label::new("local").unwrap())
1167        );
1168        assert_eq!(*parsed.location().addr(), ChannelAddr::Local(0));
1169    }
1170
1171    #[test]
1172    fn test_actor_ref_fromstr_missing_separator() {
1173        let err = ActorId::new(
1174            Uid::Instance(0xabc123, None),
1175            ProcId::new(Uid::Instance(0xdef456, None), None),
1176            None,
1177        )
1178        .to_string()
1179        .parse::<ActorAddr>()
1180        .unwrap_err();
1181        assert!(matches!(err, AddrParseError::MissingSeparator));
1182    }
1183
1184    #[test]
1185    fn test_actor_ref_fromstr_invalid_location() {
1186        let err = "local.local@tcp://".parse::<ActorAddr>().unwrap_err();
1187        assert!(matches!(err, AddrParseError::InvalidLocation(_)));
1188    }
1189
1190    #[test]
1191    fn test_proc_ref_eq_and_hash() {
1192        use std::collections::hash_map::DefaultHasher;
1193        use std::hash::Hasher;
1194
1195        let pid = ProcId::new(Uid::Instance(0x42, None), Some(Label::new("proc").unwrap()));
1196        let loc: Location = ChannelAddr::Local(1).into();
1197        let a = ProcAddr::new(pid.clone(), loc.clone());
1198        let b = ProcAddr::new(pid, loc);
1199        assert_eq!(a, b);
1200
1201        let hash = |r: &ProcAddr| {
1202            let mut h = DefaultHasher::new();
1203            r.hash(&mut h);
1204            h.finish()
1205        };
1206        assert_eq!(hash(&a), hash(&b));
1207    }
1208
1209    #[test]
1210    fn test_proc_ref_neq_different_location() {
1211        let pid = ProcId::new(Uid::Instance(0x42, None), Some(Label::new("proc").unwrap()));
1212        let a = ProcAddr::new(pid.clone(), ChannelAddr::Local(1).into());
1213        let b = ProcAddr::new(pid, ChannelAddr::Local(2).into());
1214        assert_ne!(a, b);
1215    }
1216
1217    #[test]
1218    fn test_actor_ref_eq_and_hash() {
1219        use std::collections::hash_map::DefaultHasher;
1220        use std::hash::Hasher;
1221
1222        let aid = ActorId::new(
1223            Uid::Instance(0x42, None),
1224            ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap())),
1225            Some(Label::new("actor").unwrap()),
1226        );
1227        let loc: Location = ChannelAddr::Local(1).into();
1228        let a = ActorAddr::new(aid.clone(), loc.clone());
1229        let b = ActorAddr::new(aid, loc);
1230        assert_eq!(a, b);
1231
1232        let hash = |r: &ActorAddr| {
1233            let mut h = DefaultHasher::new();
1234            r.hash(&mut h);
1235            h.finish()
1236        };
1237        assert_eq!(hash(&a), hash(&b));
1238    }
1239
1240    #[test]
1241    fn test_proc_ref_singleton() {
1242        let pid = ProcId::new(
1243            Uid::singleton(Label::new("my-proc").unwrap()),
1244            Some(Label::new("my-proc").unwrap()),
1245        );
1246        let loc: Location = ChannelAddr::Local(0).into();
1247        let pref = ProcAddr::new(pid, loc);
1248        let s = pref.to_string();
1249        assert_eq!(s, "my-proc@inproc://0");
1250        let parsed: ProcAddr = s.parse().unwrap();
1251        assert_eq!(pref, parsed);
1252    }
1253
1254    #[test]
1255    fn test_reference_prefix_relationships() {
1256        let proc_ref = ProcAddr::singleton(ChannelAddr::Local(42), "service");
1257        let actor_ref = proc_ref.actor_addr("host_agent");
1258        let port_ref = actor_ref.port_addr(Port::from(7u64));
1259
1260        assert!(Addr::Proc(proc_ref.clone()).is_prefix_of(&Addr::Actor(actor_ref.clone())));
1261        assert!(Addr::Proc(proc_ref.clone()).is_prefix_of(&Addr::Port(port_ref.clone())));
1262        assert!(Addr::Actor(actor_ref.clone()).is_prefix_of(&Addr::Port(port_ref)));
1263    }
1264
1265    #[test]
1266    fn test_location_serde_roundtrip() {
1267        let loc: Location = ChannelAddr::Local(42).into();
1268        let json = serde_json::to_string(&loc).unwrap();
1269        let parsed: Location = serde_json::from_str(&json).unwrap();
1270        assert_eq!(loc, parsed);
1271    }
1272
1273    #[test]
1274    fn test_proc_ref_serde_roundtrip() {
1275        let pid = ProcId::new(
1276            Uid::Instance(0xabcdef, None),
1277            Some(Label::new("my-proc").unwrap()),
1278        );
1279        let loc: Location = ChannelAddr::Local(42).into();
1280        let pref = ProcAddr::new(pid, loc);
1281        let json = serde_json::to_string(&pref).unwrap();
1282        let parsed: ProcAddr = serde_json::from_str(&json).unwrap();
1283        assert_eq!(pref, parsed);
1284    }
1285
1286    #[test]
1287    fn test_actor_ref_serde_roundtrip() {
1288        let aid = ActorId::new(
1289            Uid::Instance(0xabcdef, None),
1290            ProcId::new(
1291                Uid::Instance(0x123456, None),
1292                Some(Label::new("my-proc").unwrap()),
1293            ),
1294            Some(Label::new("my-actor").unwrap()),
1295        );
1296        let loc: Location = ChannelAddr::Local(42).into();
1297        let aref = ActorAddr::new(aid, loc);
1298        let json = serde_json::to_string(&aref).unwrap();
1299        let parsed: ActorAddr = serde_json::from_str(&json).unwrap();
1300        assert_eq!(aref, parsed);
1301    }
1302
1303    #[test]
1304    fn test_proc_ref_with_metatls_location() {
1305        use crate::channel::TlsAddr;
1306
1307        let pid = ProcId::new(Uid::Instance(0x42, None), None);
1308        let loc: Location = ChannelAddr::MetaTls(TlsAddr::new("example.com", 443)).into();
1309        let pref = ProcAddr::new(pid, loc);
1310        let s = pref.to_string();
1311        assert_eq!(s, format!("{}@metatls://example.com:443", pref.id()));
1312        let parsed: ProcAddr = s.parse().unwrap();
1313        assert_eq!(pref, parsed);
1314    }
1315
1316    #[test]
1317    fn test_port_ref_construction_and_accessors() {
1318        let aid = ActorId::new(
1319            Uid::Instance(0xabc123, None),
1320            ProcId::new(
1321                Uid::Instance(0xdef456, None),
1322                Some(Label::new("my-proc").unwrap()),
1323            ),
1324            Some(Label::new("my-actor").unwrap()),
1325        );
1326        let port_id = PortId::new(aid.clone(), Port::from(42));
1327        let loc: Location = ChannelAddr::Local(7).into();
1328        let pref = PortAddr::new(port_id.clone(), loc.clone());
1329        assert_eq!(pref.id(), &port_id);
1330        assert_eq!(pref.location(), &loc);
1331        assert_eq!(pref.actor_id(), &aid);
1332    }
1333
1334    #[test]
1335    fn test_port_ref_display() {
1336        let aid = ActorId::new(
1337            Uid::Instance(0xabc123, None),
1338            ProcId::new(
1339                Uid::Instance(0xdef456, None),
1340                Some(Label::new("my-proc").unwrap()),
1341            ),
1342            Some(Label::new("my-actor").unwrap()),
1343        );
1344        let port_id = PortId::new(aid, Port::from(42));
1345        let loc: Location = ChannelAddr::Local(7).into();
1346        let pref = PortAddr::new(port_id, loc);
1347        assert_eq!(pref.to_string(), format!("{}@inproc://7", pref.id()));
1348    }
1349
1350    #[test]
1351    fn test_port_ref_debug_all_labels() {
1352        let aid = ActorId::new(
1353            Uid::Instance(0xabc123, None),
1354            ProcId::new(
1355                Uid::Instance(0xdef456, None),
1356                Some(Label::new("my-proc").unwrap()),
1357            ),
1358            Some(Label::new("my-actor").unwrap()),
1359        );
1360        let port_id = PortId::new(aid, Port::from(42));
1361        let loc: Location = ChannelAddr::Local(7).into();
1362        let pref = PortAddr::new(port_id, loc);
1363        assert_eq!(
1364            format!("{:?}", pref),
1365            format!("<'my-actor.my-proc' {}@inproc://7>", pref.id())
1366        );
1367    }
1368
1369    #[test]
1370    fn test_port_ref_debug_no_labels() {
1371        let aid = ActorId::new(
1372            Uid::Instance(0xabc123, None),
1373            ProcId::new(Uid::Instance(0xdef456, None), None),
1374            None,
1375        );
1376        let port_id = PortId::new(aid, Port::from(42));
1377        let loc: Location = ChannelAddr::Local(7).into();
1378        let pref = PortAddr::new(port_id, loc);
1379        assert_eq!(format!("{:?}", pref), format!("<{}@inproc://7>", pref.id()));
1380    }
1381
1382    #[test]
1383    fn test_port_ref_debug_actor_label_only() {
1384        let aid = ActorId::new(
1385            Uid::Instance(0xabc123, None),
1386            ProcId::new(Uid::Instance(0xdef456, None), None),
1387            Some(Label::new("my-actor").unwrap()),
1388        );
1389        let port_id = PortId::new(aid, Port::from(42));
1390        let loc: Location = ChannelAddr::Local(7).into();
1391        let pref = PortAddr::new(port_id, loc);
1392        assert_eq!(
1393            format!("{:?}", pref),
1394            format!("<'my-actor' {}@inproc://7>", pref.id())
1395        );
1396    }
1397
1398    #[test]
1399    fn test_port_ref_debug_proc_label_only() {
1400        let aid = ActorId::new(
1401            Uid::Instance(0xabc123, None),
1402            ProcId::new(
1403                Uid::Instance(0xdef456, None),
1404                Some(Label::new("my-proc").unwrap()),
1405            ),
1406            None,
1407        );
1408        let port_id = PortId::new(aid, Port::from(42));
1409        let loc: Location = ChannelAddr::Local(7).into();
1410        let pref = PortAddr::new(port_id, loc);
1411        assert_eq!(
1412            format!("{:?}", pref),
1413            format!("<'.my-proc' {}@inproc://7>", pref.id())
1414        );
1415    }
1416
1417    #[test]
1418    fn test_port_ref_fromstr_roundtrip() {
1419        let aid = ActorId::new(
1420            Uid::Instance(0xabc123, None),
1421            ProcId::new(
1422                Uid::Instance(0xdef456, None),
1423                Some(Label::new("my-proc").unwrap()),
1424            ),
1425            Some(Label::new("my-actor").unwrap()),
1426        );
1427        let port_id = PortId::new(aid, Port::from(42));
1428        let loc: Location = ChannelAddr::Local(7).into();
1429        let pref = PortAddr::new(port_id, loc);
1430        let s = pref.to_string();
1431        let parsed: PortAddr = s.parse().unwrap();
1432        assert_eq!(pref, parsed);
1433        assert_eq!(
1434            parsed.id.actor_id().label().map(|l| l.as_str()),
1435            Some("my-actor")
1436        );
1437        assert_eq!(
1438            parsed.id.actor_id().proc_id().label().map(|l| l.as_str()),
1439            Some("my-proc")
1440        );
1441    }
1442
1443    #[test]
1444    fn test_port_ref_fromstr_examples() {
1445        let expected_actor_uid = Uid::Instance(0xabc123, None);
1446        let expected_proc_uid = Uid::Instance(0xdef456, None);
1447        let parsed: PortAddr = format!(
1448            "{}.{}:42@tcp://[::1]:2345",
1449            expected_actor_uid, expected_proc_uid
1450        )
1451        .parse()
1452        .unwrap();
1453        assert_eq!(parsed.id().actor_id().uid(), &expected_actor_uid);
1454        assert_eq!(parsed.id().proc_id().uid(), &expected_proc_uid);
1455        assert_eq!(parsed.id().port(), Port::from(42));
1456        assert_eq!(
1457            *parsed.location().addr(),
1458            "tcp:[::1]:2345".parse::<ChannelAddr>().unwrap()
1459        );
1460    }
1461
1462    #[test]
1463    fn test_port_ref_fromstr_missing_separator() {
1464        let err = PortId::new(
1465            ActorId::new(
1466                Uid::Instance(0xabc123, None),
1467                ProcId::new(Uid::Instance(0xdef456, None), None),
1468                None,
1469            ),
1470            Port::from(42),
1471        )
1472        .to_string()
1473        .parse::<PortAddr>()
1474        .unwrap_err();
1475        assert!(matches!(err, AddrParseError::MissingSeparator));
1476    }
1477
1478    #[test]
1479    fn test_port_ref_fromstr_invalid_location() {
1480        let err = "local.local:7@tcp://".parse::<PortAddr>().unwrap_err();
1481        assert!(matches!(err, AddrParseError::InvalidLocation(_)));
1482    }
1483
1484    #[test]
1485    fn test_reference_fromstr_specificity() {
1486        let parsed: Addr = "local@inproc://0".parse().unwrap();
1487        assert!(parsed.is_proc());
1488
1489        let parsed: Addr = "local.local@inproc://0".parse().unwrap();
1490        assert!(parsed.is_actor());
1491
1492        let parsed: Addr = "local.local:7@inproc://0".parse().unwrap();
1493        assert!(parsed.is_port());
1494    }
1495
1496    #[test]
1497    fn test_reference_fromstr_rejects_malformed_specific_forms() {
1498        assert!("local.local:not-a-port@inproc://0".parse::<Addr>().is_err());
1499        assert!("local.<bad!>@inproc://0".parse::<Addr>().is_err());
1500        assert!("local@tcp://".parse::<Addr>().is_err());
1501    }
1502
1503    #[test]
1504    fn test_reference_fromstr_does_not_downcast_malformed_port_ref() {
1505        let err = "local.local:not-a-port@inproc://0"
1506            .parse::<Addr>()
1507            .unwrap_err();
1508        assert!(matches!(
1509            err,
1510            AddrParseError::InvalidId(IdParseError::InvalidPort(_))
1511        ));
1512    }
1513
1514    #[test]
1515    fn test_reference_fromstr_does_not_downcast_malformed_actor_ref() {
1516        let err = "local.<bad!>@inproc://0".parse::<Addr>().unwrap_err();
1517        assert!(matches!(
1518            err,
1519            AddrParseError::InvalidId(IdParseError::InvalidActorProcUid(_))
1520        ));
1521    }
1522
1523    #[test]
1524    fn test_port_ref_eq_and_hash() {
1525        use std::collections::hash_map::DefaultHasher;
1526        use std::hash::Hasher;
1527
1528        let aid = ActorId::new(
1529            Uid::Instance(0x42, None),
1530            ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap())),
1531            Some(Label::new("actor").unwrap()),
1532        );
1533        let port_id = PortId::new(aid, Port::from(10));
1534        let loc: Location = ChannelAddr::Local(1).into();
1535        let a = PortAddr::new(port_id.clone(), loc.clone());
1536        let b = PortAddr::new(port_id, loc);
1537        assert_eq!(a, b);
1538
1539        let hash = |r: &PortAddr| {
1540            let mut h = DefaultHasher::new();
1541            r.hash(&mut h);
1542            h.finish()
1543        };
1544        assert_eq!(hash(&a), hash(&b));
1545    }
1546
1547    #[test]
1548    fn test_port_ref_neq_different_location() {
1549        let aid = ActorId::new(
1550            Uid::Instance(0x42, None),
1551            ProcId::new(Uid::Instance(0x99, None), Some(Label::new("proc").unwrap())),
1552            Some(Label::new("actor").unwrap()),
1553        );
1554        let port_id = PortId::new(aid, Port::from(10));
1555        let a = PortAddr::new(port_id.clone(), ChannelAddr::Local(1).into());
1556        let b = PortAddr::new(port_id, ChannelAddr::Local(2).into());
1557        assert_ne!(a, b);
1558    }
1559
1560    #[test]
1561    fn test_port_ref_serde_roundtrip() {
1562        let aid = ActorId::new(
1563            Uid::Instance(0xabcdef, None),
1564            ProcId::new(
1565                Uid::Instance(0x123456, None),
1566                Some(Label::new("my-proc").unwrap()),
1567            ),
1568            Some(Label::new("my-actor").unwrap()),
1569        );
1570        let port_id = PortId::new(aid, Port::from(42));
1571        let loc: Location = ChannelAddr::Local(7).into();
1572        let pref = PortAddr::new(port_id, loc);
1573        let json = serde_json::to_string(&pref).unwrap();
1574        let parsed: PortAddr = serde_json::from_str(&json).unwrap();
1575        assert_eq!(pref, parsed);
1576    }
1577}