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