hyperactor/
ref_.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
11use std::fmt;
12use std::str::FromStr;
13
14use serde::Deserialize;
15use serde::Serialize;
16
17use crate::channel::ChannelAddr;
18use crate::id::ActorId;
19use crate::id::IdParseError;
20use crate::id::PortId;
21use crate::id::ProcId;
22
23/// A network location, wrapping a [`ChannelAddr`].
24#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
25pub struct Location(ChannelAddr);
26
27impl Location {
28    /// Returns the underlying channel address.
29    pub fn addr(&self) -> &ChannelAddr {
30        &self.0
31    }
32}
33
34impl From<ChannelAddr> for Location {
35    fn from(addr: ChannelAddr) -> Self {
36        Self(addr)
37    }
38}
39
40impl fmt::Display for Location {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str(&self.0.to_zmq_url())
43    }
44}
45
46impl fmt::Debug for Location {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        fmt::Display::fmt(self, f)
49    }
50}
51
52impl FromStr for Location {
53    type Err = anyhow::Error;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        ChannelAddr::from_zmq_url(s).map(Self)
57    }
58}
59
60/// Errors that can occur when parsing a [`ProcRef`] or [`ActorRef`].
61#[derive(Debug, thiserror::Error)]
62pub enum RefParseError {
63    /// The `@` separator between id and location is missing.
64    #[error("missing '@' separator between id and location")]
65    MissingSeparator,
66    /// The id portion is invalid.
67    #[error("invalid id: {0}")]
68    InvalidId(#[from] IdParseError),
69    /// The location portion is invalid.
70    #[error("invalid location: {0}")]
71    InvalidLocation(#[source] anyhow::Error),
72}
73
74/// A process identifier paired with a network location.
75#[derive(Clone, Serialize, Deserialize)]
76pub struct ProcRef {
77    id: ProcId,
78    location: Location,
79}
80
81impl ProcRef {
82    /// Create a new [`ProcRef`].
83    pub fn new(id: ProcId, location: Location) -> Self {
84        Self { id, location }
85    }
86
87    /// Returns the process id.
88    pub fn id(&self) -> &ProcId {
89        &self.id
90    }
91
92    /// Returns the location.
93    pub fn location(&self) -> &Location {
94        &self.location
95    }
96}
97
98impl PartialEq for ProcRef {
99    fn eq(&self, other: &Self) -> bool {
100        self.id == other.id && self.location == other.location
101    }
102}
103
104impl Eq for ProcRef {}
105
106impl std::hash::Hash for ProcRef {
107    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
108        self.id.hash(state);
109        self.location.hash(state);
110    }
111}
112
113impl PartialOrd for ProcRef {
114    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
115        Some(self.cmp(other))
116    }
117}
118
119impl Ord for ProcRef {
120    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
121        self.id
122            .cmp(&other.id)
123            .then_with(|| self.location.cmp(&other.location))
124    }
125}
126
127impl fmt::Display for ProcRef {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        write!(f, "{}@{}", self.id, self.location)
130    }
131}
132
133impl fmt::Debug for ProcRef {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self.id.label() {
136            Some(label) => write!(f, "<'{}' {}@{}>", label, self.id, self.location),
137            None => write!(f, "<{}@{}>", self.id, self.location),
138        }
139    }
140}
141
142impl FromStr for ProcRef {
143    type Err = RefParseError;
144
145    fn from_str(s: &str) -> Result<Self, Self::Err> {
146        let at = s.find('@').ok_or(RefParseError::MissingSeparator)?;
147        let id: ProcId = s[..at].parse()?;
148        let location: Location = s[at + 1..]
149            .parse()
150            .map_err(RefParseError::InvalidLocation)?;
151        Ok(Self { id, location })
152    }
153}
154
155/// An actor identifier paired with a network location.
156#[derive(Clone, Serialize, Deserialize)]
157pub struct ActorRef {
158    id: ActorId,
159    location: Location,
160}
161
162impl ActorRef {
163    /// Create a new [`ActorRef`].
164    pub fn new(id: ActorId, location: Location) -> Self {
165        Self { id, location }
166    }
167
168    /// Returns the actor id.
169    pub fn id(&self) -> &ActorId {
170        &self.id
171    }
172
173    /// Returns the location.
174    pub fn location(&self) -> &Location {
175        &self.location
176    }
177}
178
179impl PartialEq for ActorRef {
180    fn eq(&self, other: &Self) -> bool {
181        self.id == other.id && self.location == other.location
182    }
183}
184
185impl Eq for ActorRef {}
186
187impl std::hash::Hash for ActorRef {
188    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
189        self.id.hash(state);
190        self.location.hash(state);
191    }
192}
193
194impl PartialOrd for ActorRef {
195    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
196        Some(self.cmp(other))
197    }
198}
199
200impl Ord for ActorRef {
201    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
202        self.id
203            .cmp(&other.id)
204            .then_with(|| self.location.cmp(&other.location))
205    }
206}
207
208impl fmt::Display for ActorRef {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        write!(f, "{}@{}", self.id, self.location)
211    }
212}
213
214impl fmt::Debug for ActorRef {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        match (self.id.label(), self.id.proc_id().label()) {
217            (Some(actor_label), Some(proc_label)) => {
218                write!(
219                    f,
220                    "<'{}.{}' {}@{}>",
221                    actor_label, proc_label, self.id, self.location
222                )
223            }
224            (Some(actor_label), None) => {
225                write!(f, "<'{}' {}@{}>", actor_label, self.id, self.location)
226            }
227            (None, Some(proc_label)) => {
228                write!(f, "<'.{}' {}@{}>", proc_label, self.id, self.location)
229            }
230            (None, None) => {
231                write!(f, "<{}@{}>", self.id, self.location)
232            }
233        }
234    }
235}
236
237impl FromStr for ActorRef {
238    type Err = RefParseError;
239
240    fn from_str(s: &str) -> Result<Self, Self::Err> {
241        let at = s.find('@').ok_or(RefParseError::MissingSeparator)?;
242        let id: ActorId = s[..at].parse()?;
243        let location: Location = s[at + 1..]
244            .parse()
245            .map_err(RefParseError::InvalidLocation)?;
246        Ok(Self { id, location })
247    }
248}
249
250/// A port identifier paired with a network location.
251#[derive(Clone, Serialize, Deserialize)]
252pub struct PortRef {
253    id: PortId,
254    location: Location,
255}
256
257impl PortRef {
258    /// Create a new [`PortRef`].
259    pub fn new(id: PortId, location: Location) -> Self {
260        Self { id, location }
261    }
262
263    /// Returns the port id.
264    pub fn id(&self) -> &PortId {
265        &self.id
266    }
267
268    /// Returns the location.
269    pub fn location(&self) -> &Location {
270        &self.location
271    }
272
273    /// Returns the actor id (delegates to port id).
274    pub fn actor_id(&self) -> &ActorId {
275        self.id.actor_id()
276    }
277}
278
279impl PartialEq for PortRef {
280    fn eq(&self, other: &Self) -> bool {
281        self.id == other.id && self.location == other.location
282    }
283}
284
285impl Eq for PortRef {}
286
287impl std::hash::Hash for PortRef {
288    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
289        self.id.hash(state);
290        self.location.hash(state);
291    }
292}
293
294impl PartialOrd for PortRef {
295    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
296        Some(self.cmp(other))
297    }
298}
299
300impl Ord for PortRef {
301    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
302        self.id
303            .cmp(&other.id)
304            .then_with(|| self.location.cmp(&other.location))
305    }
306}
307
308impl fmt::Display for PortRef {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        write!(f, "{}@{}", self.id, self.location)
311    }
312}
313
314impl fmt::Debug for PortRef {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        match (
317            self.id.actor_id().label(),
318            self.id.actor_id().proc_id().label(),
319        ) {
320            (Some(actor_label), Some(proc_label)) => {
321                write!(
322                    f,
323                    "<'{}.{}' {}@{}>",
324                    actor_label, proc_label, self.id, self.location
325                )
326            }
327            (Some(actor_label), None) => {
328                write!(f, "<'{}' {}@{}>", actor_label, self.id, self.location)
329            }
330            (None, Some(proc_label)) => {
331                write!(f, "<'.{}' {}@{}>", proc_label, self.id, self.location)
332            }
333            (None, None) => {
334                write!(f, "<{}@{}>", self.id, self.location)
335            }
336        }
337    }
338}
339
340impl FromStr for PortRef {
341    type Err = RefParseError;
342
343    fn from_str(s: &str) -> Result<Self, Self::Err> {
344        let at = s.find('@').ok_or(RefParseError::MissingSeparator)?;
345        let id: PortId = s[..at].parse()?;
346        let location: Location = s[at + 1..]
347            .parse()
348            .map_err(RefParseError::InvalidLocation)?;
349        Ok(Self { id, location })
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use std::hash::Hash;
356
357    use super::*;
358    use crate::id::Label;
359    use crate::id::Uid;
360    use crate::port::Port;
361
362    #[test]
363    fn test_location_display_fromstr_roundtrip() {
364        let loc: Location = ChannelAddr::Local(42).into();
365        let s = loc.to_string();
366        assert_eq!(s, "inproc://42");
367        let parsed: Location = s.parse().unwrap();
368        assert_eq!(loc, parsed);
369    }
370
371    #[test]
372    fn test_location_tcp() {
373        let addr: ChannelAddr = "tcp:127.0.0.1:8080".parse().unwrap();
374        let loc = Location::from(addr.clone());
375        assert_eq!(loc.to_string(), "tcp://127.0.0.1:8080");
376        assert_eq!(loc.addr(), &addr);
377    }
378
379    #[test]
380    fn test_location_debug_same_as_display() {
381        let loc: Location = ChannelAddr::Local(7).into();
382        assert_eq!(format!("{:?}", loc), format!("{}", loc));
383    }
384
385    #[test]
386    fn test_proc_ref_display() {
387        let pid = ProcId::new(
388            Uid::Instance(0xabc123),
389            Some(Label::new("my-proc").unwrap()),
390        );
391        let loc: Location = ChannelAddr::Local(42).into();
392        let pref = ProcRef::new(pid, loc);
393        assert_eq!(pref.to_string(), "0000000000abc123@inproc://42");
394    }
395
396    #[test]
397    fn test_proc_ref_debug_with_label() {
398        let pid = ProcId::new(
399            Uid::Instance(0xabc123),
400            Some(Label::new("my-proc").unwrap()),
401        );
402        let loc: Location = ChannelAddr::Local(42).into();
403        let pref = ProcRef::new(pid, loc);
404        assert_eq!(
405            format!("{:?}", pref),
406            "<'my-proc' 0000000000abc123@inproc://42>"
407        );
408    }
409
410    #[test]
411    fn test_proc_ref_debug_without_label() {
412        let pid = ProcId::new(Uid::Instance(0xabc123), None);
413        let loc: Location = ChannelAddr::Local(42).into();
414        let pref = ProcRef::new(pid, loc);
415        assert_eq!(format!("{:?}", pref), "<0000000000abc123@inproc://42>");
416    }
417
418    #[test]
419    fn test_proc_ref_fromstr_roundtrip() {
420        let pid = ProcId::new(
421            Uid::Instance(0xabc123),
422            Some(Label::new("my-proc").unwrap()),
423        );
424        let loc: Location = ChannelAddr::Local(42).into();
425        let pref = ProcRef::new(pid, loc);
426        let s = pref.to_string();
427        let parsed: ProcRef = s.parse().unwrap();
428        assert_eq!(pref, parsed);
429    }
430
431    #[test]
432    fn test_proc_ref_fromstr_tcp() {
433        let parsed: ProcRef = "0000000000abc123@tcp://127.0.0.1:8080".parse().unwrap();
434        assert_eq!(*parsed.id().uid(), Uid::Instance(0xabc123));
435        assert_eq!(
436            *parsed.location().addr(),
437            "tcp:127.0.0.1:8080".parse::<ChannelAddr>().unwrap()
438        );
439    }
440
441    #[test]
442    fn test_proc_ref_fromstr_missing_separator() {
443        let err = "0000000000abc123".parse::<ProcRef>().unwrap_err();
444        assert!(matches!(err, RefParseError::MissingSeparator));
445    }
446
447    #[test]
448    fn test_actor_ref_display() {
449        let aid = ActorId::new(
450            Uid::Instance(0xabc123),
451            ProcId::new(
452                Uid::Instance(0xdef456),
453                Some(Label::new("my-proc").unwrap()),
454            ),
455            Some(Label::new("my-actor").unwrap()),
456        );
457        let loc: Location = ChannelAddr::Local(42).into();
458        let aref = ActorRef::new(aid, loc);
459        assert_eq!(
460            aref.to_string(),
461            "0000000000abc123.0000000000def456@inproc://42"
462        );
463    }
464
465    #[test]
466    fn test_actor_ref_debug_all_labels() {
467        let aid = ActorId::new(
468            Uid::Instance(0xabc123),
469            ProcId::new(
470                Uid::Instance(0xdef456),
471                Some(Label::new("my-proc").unwrap()),
472            ),
473            Some(Label::new("my-actor").unwrap()),
474        );
475        let loc: Location = ChannelAddr::Local(42).into();
476        let aref = ActorRef::new(aid, loc);
477        assert_eq!(
478            format!("{:?}", aref),
479            "<'my-actor.my-proc' 0000000000abc123.0000000000def456@inproc://42>"
480        );
481    }
482
483    #[test]
484    fn test_actor_ref_debug_no_labels() {
485        let aid = ActorId::new(
486            Uid::Instance(0xabc123),
487            ProcId::new(Uid::Instance(0xdef456), None),
488            None,
489        );
490        let loc: Location = ChannelAddr::Local(42).into();
491        let aref = ActorRef::new(aid, loc);
492        assert_eq!(
493            format!("{:?}", aref),
494            "<0000000000abc123.0000000000def456@inproc://42>"
495        );
496    }
497
498    #[test]
499    fn test_actor_ref_debug_actor_label_only() {
500        let aid = ActorId::new(
501            Uid::Instance(0xabc123),
502            ProcId::new(Uid::Instance(0xdef456), None),
503            Some(Label::new("my-actor").unwrap()),
504        );
505        let loc: Location = ChannelAddr::Local(42).into();
506        let aref = ActorRef::new(aid, loc);
507        assert_eq!(
508            format!("{:?}", aref),
509            "<'my-actor' 0000000000abc123.0000000000def456@inproc://42>"
510        );
511    }
512
513    #[test]
514    fn test_actor_ref_debug_proc_label_only() {
515        let aid = ActorId::new(
516            Uid::Instance(0xabc123),
517            ProcId::new(
518                Uid::Instance(0xdef456),
519                Some(Label::new("my-proc").unwrap()),
520            ),
521            None,
522        );
523        let loc: Location = ChannelAddr::Local(42).into();
524        let aref = ActorRef::new(aid, loc);
525        assert_eq!(
526            format!("{:?}", aref),
527            "<'.my-proc' 0000000000abc123.0000000000def456@inproc://42>"
528        );
529    }
530
531    #[test]
532    fn test_actor_ref_fromstr_roundtrip() {
533        let aid = ActorId::new(
534            Uid::Instance(0xabc123),
535            ProcId::new(
536                Uid::Instance(0xdef456),
537                Some(Label::new("my-proc").unwrap()),
538            ),
539            Some(Label::new("my-actor").unwrap()),
540        );
541        let loc: Location = ChannelAddr::Local(42).into();
542        let aref = ActorRef::new(aid, loc);
543        let s = aref.to_string();
544        let parsed: ActorRef = s.parse().unwrap();
545        assert_eq!(aref, parsed);
546    }
547
548    #[test]
549    fn test_actor_ref_fromstr_missing_separator() {
550        let err = "0000000000abc123.0000000000def456"
551            .parse::<ActorRef>()
552            .unwrap_err();
553        assert!(matches!(err, RefParseError::MissingSeparator));
554    }
555
556    #[test]
557    fn test_proc_ref_eq_and_hash() {
558        use std::collections::hash_map::DefaultHasher;
559        use std::hash::Hasher;
560
561        let pid = ProcId::new(Uid::Instance(0x42), Some(Label::new("proc").unwrap()));
562        let loc: Location = ChannelAddr::Local(1).into();
563        let a = ProcRef::new(pid.clone(), loc.clone());
564        let b = ProcRef::new(pid, loc);
565        assert_eq!(a, b);
566
567        let hash = |r: &ProcRef| {
568            let mut h = DefaultHasher::new();
569            r.hash(&mut h);
570            h.finish()
571        };
572        assert_eq!(hash(&a), hash(&b));
573    }
574
575    #[test]
576    fn test_proc_ref_neq_different_location() {
577        let pid = ProcId::new(Uid::Instance(0x42), Some(Label::new("proc").unwrap()));
578        let a = ProcRef::new(pid.clone(), ChannelAddr::Local(1).into());
579        let b = ProcRef::new(pid, ChannelAddr::Local(2).into());
580        assert_ne!(a, b);
581    }
582
583    #[test]
584    fn test_actor_ref_eq_and_hash() {
585        use std::collections::hash_map::DefaultHasher;
586        use std::hash::Hasher;
587
588        let aid = ActorId::new(
589            Uid::Instance(0x42),
590            ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap())),
591            Some(Label::new("actor").unwrap()),
592        );
593        let loc: Location = ChannelAddr::Local(1).into();
594        let a = ActorRef::new(aid.clone(), loc.clone());
595        let b = ActorRef::new(aid, loc);
596        assert_eq!(a, b);
597
598        let hash = |r: &ActorRef| {
599            let mut h = DefaultHasher::new();
600            r.hash(&mut h);
601            h.finish()
602        };
603        assert_eq!(hash(&a), hash(&b));
604    }
605
606    #[test]
607    fn test_proc_ref_singleton() {
608        let pid = ProcId::new(
609            Uid::singleton(Label::new("my-proc").unwrap()),
610            Some(Label::new("my-proc").unwrap()),
611        );
612        let loc: Location = ChannelAddr::Local(0).into();
613        let pref = ProcRef::new(pid, loc);
614        let s = pref.to_string();
615        assert_eq!(s, "_my-proc@inproc://0");
616        let parsed: ProcRef = s.parse().unwrap();
617        assert_eq!(pref, parsed);
618    }
619
620    #[test]
621    fn test_location_serde_roundtrip() {
622        let loc: Location = ChannelAddr::Local(42).into();
623        let json = serde_json::to_string(&loc).unwrap();
624        let parsed: Location = serde_json::from_str(&json).unwrap();
625        assert_eq!(loc, parsed);
626    }
627
628    #[test]
629    fn test_proc_ref_serde_roundtrip() {
630        let pid = ProcId::new(
631            Uid::Instance(0xabcdef),
632            Some(Label::new("my-proc").unwrap()),
633        );
634        let loc: Location = ChannelAddr::Local(42).into();
635        let pref = ProcRef::new(pid, loc);
636        let json = serde_json::to_string(&pref).unwrap();
637        let parsed: ProcRef = serde_json::from_str(&json).unwrap();
638        assert_eq!(pref, parsed);
639    }
640
641    #[test]
642    fn test_actor_ref_serde_roundtrip() {
643        let aid = ActorId::new(
644            Uid::Instance(0xabcdef),
645            ProcId::new(
646                Uid::Instance(0x123456),
647                Some(Label::new("my-proc").unwrap()),
648            ),
649            Some(Label::new("my-actor").unwrap()),
650        );
651        let loc: Location = ChannelAddr::Local(42).into();
652        let aref = ActorRef::new(aid, loc);
653        let json = serde_json::to_string(&aref).unwrap();
654        let parsed: ActorRef = serde_json::from_str(&json).unwrap();
655        assert_eq!(aref, parsed);
656    }
657
658    #[test]
659    fn test_proc_ref_with_metatls_location() {
660        use crate::channel::TlsAddr;
661
662        let pid = ProcId::new(Uid::Instance(0x42), None);
663        let loc: Location = ChannelAddr::MetaTls(TlsAddr::new("example.com", 443)).into();
664        let pref = ProcRef::new(pid, loc);
665        let s = pref.to_string();
666        assert_eq!(s, "0000000000000042@metatls://example.com:443");
667        let parsed: ProcRef = s.parse().unwrap();
668        assert_eq!(pref, parsed);
669    }
670
671    #[test]
672    fn test_port_ref_construction_and_accessors() {
673        let aid = ActorId::new(
674            Uid::Instance(0xabc123),
675            ProcId::new(
676                Uid::Instance(0xdef456),
677                Some(Label::new("my-proc").unwrap()),
678            ),
679            Some(Label::new("my-actor").unwrap()),
680        );
681        let port_id = PortId::new(aid.clone(), Port::from(42));
682        let loc: Location = ChannelAddr::Local(7).into();
683        let pref = PortRef::new(port_id.clone(), loc.clone());
684        assert_eq!(pref.id(), &port_id);
685        assert_eq!(pref.location(), &loc);
686        assert_eq!(pref.actor_id(), &aid);
687    }
688
689    #[test]
690    fn test_port_ref_display() {
691        let aid = ActorId::new(
692            Uid::Instance(0xabc123),
693            ProcId::new(
694                Uid::Instance(0xdef456),
695                Some(Label::new("my-proc").unwrap()),
696            ),
697            Some(Label::new("my-actor").unwrap()),
698        );
699        let port_id = PortId::new(aid, Port::from(42));
700        let loc: Location = ChannelAddr::Local(7).into();
701        let pref = PortRef::new(port_id, loc);
702        assert_eq!(
703            pref.to_string(),
704            "0000000000abc123.0000000000def456:42@inproc://7"
705        );
706    }
707
708    #[test]
709    fn test_port_ref_debug_all_labels() {
710        let aid = ActorId::new(
711            Uid::Instance(0xabc123),
712            ProcId::new(
713                Uid::Instance(0xdef456),
714                Some(Label::new("my-proc").unwrap()),
715            ),
716            Some(Label::new("my-actor").unwrap()),
717        );
718        let port_id = PortId::new(aid, Port::from(42));
719        let loc: Location = ChannelAddr::Local(7).into();
720        let pref = PortRef::new(port_id, loc);
721        assert_eq!(
722            format!("{:?}", pref),
723            "<'my-actor.my-proc' 0000000000abc123.0000000000def456:42@inproc://7>"
724        );
725    }
726
727    #[test]
728    fn test_port_ref_debug_no_labels() {
729        let aid = ActorId::new(
730            Uid::Instance(0xabc123),
731            ProcId::new(Uid::Instance(0xdef456), None),
732            None,
733        );
734        let port_id = PortId::new(aid, Port::from(42));
735        let loc: Location = ChannelAddr::Local(7).into();
736        let pref = PortRef::new(port_id, loc);
737        assert_eq!(
738            format!("{:?}", pref),
739            "<0000000000abc123.0000000000def456:42@inproc://7>"
740        );
741    }
742
743    #[test]
744    fn test_port_ref_debug_actor_label_only() {
745        let aid = ActorId::new(
746            Uid::Instance(0xabc123),
747            ProcId::new(Uid::Instance(0xdef456), None),
748            Some(Label::new("my-actor").unwrap()),
749        );
750        let port_id = PortId::new(aid, Port::from(42));
751        let loc: Location = ChannelAddr::Local(7).into();
752        let pref = PortRef::new(port_id, loc);
753        assert_eq!(
754            format!("{:?}", pref),
755            "<'my-actor' 0000000000abc123.0000000000def456:42@inproc://7>"
756        );
757    }
758
759    #[test]
760    fn test_port_ref_debug_proc_label_only() {
761        let aid = ActorId::new(
762            Uid::Instance(0xabc123),
763            ProcId::new(
764                Uid::Instance(0xdef456),
765                Some(Label::new("my-proc").unwrap()),
766            ),
767            None,
768        );
769        let port_id = PortId::new(aid, Port::from(42));
770        let loc: Location = ChannelAddr::Local(7).into();
771        let pref = PortRef::new(port_id, loc);
772        assert_eq!(
773            format!("{:?}", pref),
774            "<'.my-proc' 0000000000abc123.0000000000def456:42@inproc://7>"
775        );
776    }
777
778    #[test]
779    fn test_port_ref_fromstr_roundtrip() {
780        let aid = ActorId::new(
781            Uid::Instance(0xabc123),
782            ProcId::new(
783                Uid::Instance(0xdef456),
784                Some(Label::new("my-proc").unwrap()),
785            ),
786            Some(Label::new("my-actor").unwrap()),
787        );
788        let port_id = PortId::new(aid, Port::from(42));
789        let loc: Location = ChannelAddr::Local(7).into();
790        let pref = PortRef::new(port_id, loc);
791        let s = pref.to_string();
792        let parsed: PortRef = s.parse().unwrap();
793        assert_eq!(pref, parsed);
794    }
795
796    #[test]
797    fn test_port_ref_fromstr_missing_separator() {
798        let err = "0000000000abc123.0000000000def456:42"
799            .parse::<PortRef>()
800            .unwrap_err();
801        assert!(matches!(err, RefParseError::MissingSeparator));
802    }
803
804    #[test]
805    fn test_port_ref_eq_and_hash() {
806        use std::collections::hash_map::DefaultHasher;
807        use std::hash::Hasher;
808
809        let aid = ActorId::new(
810            Uid::Instance(0x42),
811            ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap())),
812            Some(Label::new("actor").unwrap()),
813        );
814        let port_id = PortId::new(aid, Port::from(10));
815        let loc: Location = ChannelAddr::Local(1).into();
816        let a = PortRef::new(port_id.clone(), loc.clone());
817        let b = PortRef::new(port_id, loc);
818        assert_eq!(a, b);
819
820        let hash = |r: &PortRef| {
821            let mut h = DefaultHasher::new();
822            r.hash(&mut h);
823            h.finish()
824        };
825        assert_eq!(hash(&a), hash(&b));
826    }
827
828    #[test]
829    fn test_port_ref_neq_different_location() {
830        let aid = ActorId::new(
831            Uid::Instance(0x42),
832            ProcId::new(Uid::Instance(0x99), Some(Label::new("proc").unwrap())),
833            Some(Label::new("actor").unwrap()),
834        );
835        let port_id = PortId::new(aid, Port::from(10));
836        let a = PortRef::new(port_id.clone(), ChannelAddr::Local(1).into());
837        let b = PortRef::new(port_id, ChannelAddr::Local(2).into());
838        assert_ne!(a, b);
839    }
840
841    #[test]
842    fn test_port_ref_serde_roundtrip() {
843        let aid = ActorId::new(
844            Uid::Instance(0xabcdef),
845            ProcId::new(
846                Uid::Instance(0x123456),
847                Some(Label::new("my-proc").unwrap()),
848            ),
849            Some(Label::new("my-actor").unwrap()),
850        );
851        let port_id = PortId::new(aid, Port::from(42));
852        let loc: Location = ChannelAddr::Local(7).into();
853        let pref = PortRef::new(port_id, loc);
854        let json = serde_json::to_string(&pref).unwrap();
855        let parsed: PortRef = serde_json::from_str(&json).unwrap();
856        assert_eq!(pref, parsed);
857    }
858}