kompact 0.12.0

Kompact is a Rust implementation of the Kompics component model combined with the Actor model.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
use super::*;
use std::{
    convert::TryFrom,
    error::Error,
    fmt::{self, Debug},
    net::{AddrParseError, IpAddr, SocketAddr},
    ops::Div,
    str::FromStr,
};
use uuid::Uuid;

/// Transport protocol to use for delivering messages
/// sent to an [ActorPath](ActorPath)
///
/// # Note
///
/// Dispatcher implementations are not required to implement all protocols.
/// Check your concrete implementation, before selecting an arbitrary protocol.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(u8)]
pub enum Transport {
    /// Local reflection only, no network messages involved
    Local = 0b00,
    /// Send messages over TCP
    Tcp = 0b01,
    /// Send messages as UDP datagrams
    Udp = 0b10,
}

impl Transport {
    /// Returns `true` if this is an instance of [Transport::Local](Transport::Local)
    pub fn is_local(&self) -> bool {
        matches!(*self, Transport::Local)
    }

    /// Returns `true` if this is *not* an instance of [Transport::Local](Transport::Local)
    pub fn is_remote(&self) -> bool {
        !self.is_local()
    }
}

impl fmt::Display for Transport {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            &Transport::Local => write!(fmt, "local"),
            &Transport::Tcp => write!(fmt, "tcp"),
            &Transport::Udp => write!(fmt, "udp"),
        }
    }
}

impl FromStr for Transport {
    type Err = TransportParseError;

    fn from_str(s: &str) -> Result<Transport, TransportParseError> {
        match s {
            "local" => Ok(Transport::Local),
            "tcp" => Ok(Transport::Tcp),
            "udp" => Ok(Transport::Udp),
            _ => Err(TransportParseError),
        }
    }
}

/// Error type for parsing the [Transport](Transport) from a string
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct TransportParseError;

impl fmt::Display for TransportParseError {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.write_str("TransportParseError")
    }
}

impl Error for TransportParseError {}

/// Error type for parsing [paths](ActorPath) from a string
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PathParseError {
    /// The format is wrong
    Form(String),
    /// The transport protocol was invalid
    Transport(TransportParseError),
    /// The network address was invalid
    Addr(AddrParseError),
    /// The path contains a disallowed character
    IllegalCharacter(char),
}

impl fmt::Display for PathParseError {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PathParseError::Form(s) => write!(fmt, "Invalid formatting: {}", s),
            PathParseError::Transport(e) => write!(fmt, "Could not parse transport: {}", e),
            PathParseError::Addr(e) => write!(fmt, "Could not parse address: {}", e),
            PathParseError::IllegalCharacter(c) => {
                write!(fmt, "The path contains an illegal character: {}", c)
            }
        }
    }
}

impl Error for PathParseError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            PathParseError::Form(_) => None,
            PathParseError::Transport(e) => Some(e),
            PathParseError::Addr(e) => Some(e),
            PathParseError::IllegalCharacter(_) => None,
        }
    }
}

impl From<TransportParseError> for PathParseError {
    fn from(e: TransportParseError) -> PathParseError {
        PathParseError::Transport(e)
    }
}

impl From<AddrParseError> for PathParseError {
    fn from(e: AddrParseError) -> PathParseError {
        PathParseError::Addr(e)
    }
}

/// The part of an [ActorPath](ActorPath) that refers to the [KompactSystem](KompactSystem)
///
/// As a URI, a `SystemPath` looks like `"tcp://127.0.0.1:8080"`, for example.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SystemPath {
    protocol: Transport,
    // TODO address could be IPv4, IPv6, or a domain name (not supported yet)
    address: IpAddr,
    port: u16,
}

impl SystemPath {
    /// Construct a new system path from individual parts
    pub fn new(protocol: Transport, address: IpAddr, port: u16) -> SystemPath {
        SystemPath {
            protocol,
            address,
            port,
        }
    }

    /// Construct a new system path from individual parts using a [SocketAddr](std::net::SocketAddr)
    pub fn with_socket(protocol: Transport, socket: SocketAddr) -> SystemPath {
        SystemPath {
            protocol,
            address: socket.ip(),
            port: socket.port(),
        }
    }

    /// Returns a reference to the [Transport](Transport) protocol associated with with this system path
    pub fn protocol(&self) -> Transport {
        self.protocol
    }

    /// Returns a reference to the IP address associated with with this system path
    pub fn address(&self) -> &IpAddr {
        &self.address
    }

    /// Returns the port associated with with this system path
    pub fn port(&self) -> u16 {
        self.port
    }

    /// Create a named path starting with this system path and ending with the given string
    ///
    /// Paths created with this function will be validated to be a valid lookup path,
    /// and an error will be returned if they are not.
    pub fn into_named_with_string(self, path: &str) -> Result<NamedPath, PathParseError> {
        let parsed = parse_path(path);
        self.into_named_with_vec(parsed)
    }

    /// Returns the SocketAddr corresponding to the SystemPath
    pub fn socket_address(&self) -> SocketAddr {
        SocketAddr::new(self.address, self.port)
    }

    /// Create a named path starting with this system path and ending with the sequence of path segments
    ///
    /// Paths created with this function will be validated to be a valid lookup path,
    /// and an error will be returned if they are not.
    pub fn into_named_with_vec(self, path: Vec<String>) -> Result<NamedPath, PathParseError> {
        validate_lookup_path(&path)?;
        let named = NamedPath::with_system(self, path);
        Ok(named)
    }

    /// Create a unique path starting with this system path and ending with the given `id`
    pub fn into_unique(self, id: Uuid) -> UniquePath {
        UniquePath::with_system(self, id)
    }
}

impl fmt::Display for SystemPath {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(fmt, "{}://{}:{}", self.protocol, self.address, self.port)
    }
}

/// Methods for things that contain a [SystemPath](SystemPath)
pub trait SystemField {
    /// Returns a reference to the system path
    fn system(&self) -> &SystemPath;

    /// Returns the transport protocol used in the system path
    fn protocol(&self) -> Transport {
        self.system().protocol()
    }

    /// Returns the address used in the system path
    fn address(&self) -> &IpAddr {
        self.system().address()
    }

    /// Returns the port used in the system path
    #[cfg_attr(not(feature = "distributed"), allow(dead_code))]
    fn port(&self) -> u16 {
        self.system().port()
    }
}

impl SystemField for SystemPath {
    fn system(&self) -> &SystemPath {
        self
    }
}

/// An actor path is a serialisable, possibly remote reference to
/// an actor
///
/// Any message sent via an actor path *might* go over the network,
/// and must be treated as fallible.
/// It must also be [serialisable](Serialisable).
#[derive(Clone, Debug)]
#[repr(u8)]
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ActorPath {
    /// A unique actor path identifies a concrete instance of an actor
    ///
    /// Unique actor paths use the component's unique id internally.
    ///
    /// Unique actor paths become invalid when a component is
    /// replaced with a new instance of the same type.
    ///
    /// A unique path may look something like `"tcp://127.0.0.1:8080#1e555f40-de1d-4aee-8202-64fdc27edfa8"`, for example.
    Unique(UniquePath),
    /// A named actor path identifies a service, rather than a concrete actor instance
    ///
    /// Named paths must be [registered](KompactSystem::register_by_alias) to a particular actor,
    /// and their registration can be changed over time,
    /// as actors fail and are replaced, for example.
    ///
    /// Named paths may be described hierarchically, similar to URLs.
    ///
    /// A named path may look something like `"tcp://127.0.0.1:8080/my-actor-group/my-actor"`, for example.
    Named(NamedPath),
}

impl ActorPath {
    fn system_mut(&mut self) -> &mut SystemPath {
        match self {
            ActorPath::Unique(up) => up.system_mut(),
            ActorPath::Named(np) => np.system_mut(),
        }
    }

    /// Change the transport protocol for this actor path
    pub fn set_protocol(&mut self, proto: Transport) {
        self.system_mut().protocol = proto;
    }

    /// Sets the transport protocol for this actor path to UDP
    pub fn via_udp(&mut self) {
        self.set_protocol(Transport::Udp);
    }

    /// Sets the transport protocol for this actor path to TCP
    pub fn via_tcp(&mut self) {
        self.set_protocol(Transport::Tcp);
    }

    /// Sets the transport protocol for this actor path to LOCAL
    pub fn via_local(&mut self) {
        self.set_protocol(Transport::Local);
    }

    /// If this path is a named path, return the underlying named path
    ///
    /// Otherwise return `None`.
    pub fn named(self) -> Option<NamedPath> {
        match self {
            ActorPath::Named(p) => Some(p),
            _ => None,
        }
    }

    /// Return the underlying named path
    ///
    /// Panics if this is not actually a named path variant.
    pub fn unwrap_named(self) -> NamedPath {
        self.named().unwrap()
    }

    /// If this path is a unique path, return the underlying unique path
    ///
    /// Otherwise return `None`.
    pub fn unique(self) -> Option<UniquePath> {
        match self {
            ActorPath::Unique(p) => Some(p),
            _ => None,
        }
    }

    /// Return the underlying unique path
    ///
    /// Panics if this is not actually a uniqe path variant.
    pub fn unwrap_unique(self) -> UniquePath {
        self.unique().unwrap()
    }
}

impl SystemField for ActorPath {
    fn system(&self) -> &SystemPath {
        match self {
            ActorPath::Unique(up) => up.system(),
            ActorPath::Named(np) => np.system(),
        }
    }
}

impl From<(SystemPath, Uuid)> for ActorPath {
    fn from(t: (SystemPath, Uuid)) -> ActorPath {
        ActorPath::Unique(UniquePath {
            system: t.0,
            id: t.1,
        })
    }
}

impl From<UniquePath> for ActorPath {
    fn from(p: UniquePath) -> ActorPath {
        ActorPath::Unique(p)
    }
}

impl From<NamedPath> for ActorPath {
    fn from(p: NamedPath) -> ActorPath {
        ActorPath::Named(p)
    }
}

/// Separator for segments in named actor paths
///
/// # Example
///
/// `tcp://127.0.0.1:8080/segment1/segment2`
pub const PATH_SEP: char = '/';
/// Separator for the id in unique actor paths
///
/// # Example
///
/// `tcp://127.0.0.1:8080#1e555f40-de1d-4aee-8202-64fdc27edfa8`
pub const UNIQUE_PATH_SEP: char = '#';
/// Marker for broadcast routing
///
/// # Example
///
/// `tcp://127.0.0.1:8080/segment1/*`
/// will route to all actors whose path starts with `segment1`.
pub const BROADCAST_MARKER: char = '*';
/// Marker for select routing
///
/// # Example
///
/// `tcp://127.0.0.1:8080/segment1/?`
/// will route to some actor whose path starts with `segment1`.
pub const SELECT_MARKER: char = '?';

impl fmt::Display for ActorPath {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ActorPath::Named(np) => {
                let path = np.path.iter().fold(String::new(), |mut acc, arg| {
                    acc.push(PATH_SEP);
                    acc.push_str(arg);
                    acc
                });
                write!(fmt, "{}{}", np.system, path)
            }
            ActorPath::Unique(up) => write!(fmt, "{}{}{}", up.system, UNIQUE_PATH_SEP, up.id),
        }
    }
}

impl TryFrom<String> for ActorPath {
    type Error = PathParseError;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        let p = ActorPath::from_str(&s)?;
        Ok(p)
    }
}

impl FromStr for ActorPath {
    type Err = PathParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.contains(UNIQUE_PATH_SEP) {
            let p = UniquePath::from_str(s)?;
            Ok(ActorPath::Unique(p))
        } else {
            let p = NamedPath::from_str(s)?;
            Ok(ActorPath::Named(p))
        }
    }
}

/// A unique actor path identifies a concrete instance of an actor
///
/// Unique actor paths use the component's unique id internally.
///
/// Unique actor paths become invalid when a component is
/// replaced with a new instance of the same type.
///
/// A unique path may look something like `"tcp://127.0.0.1:8080#1e555f40-de1d-4aee-8202-64fdc27edfa8"`, for example.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct UniquePath {
    system: SystemPath,
    id: Uuid,
}

impl UniquePath {
    /// Construct a new unique path from parts
    pub fn new(protocol: Transport, address: IpAddr, port: u16, id: Uuid) -> UniquePath {
        UniquePath {
            system: SystemPath::new(protocol, address, port),
            id,
        }
    }

    /// Construct a new unique path from a system path and an id
    pub fn with_system(system: SystemPath, id: Uuid) -> UniquePath {
        UniquePath { system, id }
    }

    /// Construct a new unique path with a [socket](std::net::SocketAddr) and an id
    pub fn with_socket(protocol: Transport, socket: SocketAddr, id: Uuid) -> UniquePath {
        UniquePath {
            system: SystemPath::with_socket(protocol, socket),
            id,
        }
    }

    /// Returns a reference to this path's unique id
    pub fn id(&self) -> Uuid {
        self.id
    }

    /// Returns a mutable reference to this path's system path part
    pub fn system_mut(&mut self) -> &mut SystemPath {
        &mut self.system
    }
}

impl TryFrom<String> for UniquePath {
    type Error = PathParseError;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        let p = UniquePath::from_str(&s)?;
        Ok(p)
    }
}

impl FromStr for UniquePath {
    type Err = PathParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split("://").collect();
        // parts: [tcp]://[IP:port#id]
        if parts.len() != 2 {
            return Err(PathParseError::Form(s.to_string()));
        }
        let proto: Transport = parts[0].parse()?;
        let parts: Vec<&str> = parts[1].split(UNIQUE_PATH_SEP).collect();
        // parts: [IP:port]#[UUID]
        if parts.len() != 2 {
            return Err(PathParseError::Form(s.to_string()));
        }
        let socket = SocketAddr::from_str(parts[0])?;
        let uuid =
            Uuid::from_str(parts[1]).map_err(|_parse_err| PathParseError::Form(s.to_string()))?;

        Ok(UniquePath::with_socket(proto, socket, uuid))
    }
}

impl SystemField for UniquePath {
    fn system(&self) -> &SystemPath {
        &self.system
    }
}

/// A named actor path identifies a service, rather than a concrete actor instance
///
/// Named paths must be [registered](KompactSystem::register_by_alias) to a particular actor,
/// and their registration can be changed over time,
/// as actors fail and are replaced, for example.
///
/// Named paths may be described hierarchically, similar to URLs.
///
/// A named path may look something like `"tcp://127.0.0.1:8080/my-actor-group/my-actor"`, for example.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NamedPath {
    system: SystemPath,
    path: Vec<String>,
}

impl NamedPath {
    /// Construct a new named path from parts
    ///
    /// Make sure that none of the elements of the `path` vector contain slashes (i.e., `/`),
    /// as those will be the path separators for the individual elements later.
    pub fn new(protocol: Transport, address: IpAddr, port: u16, path: Vec<String>) -> NamedPath {
        debug_assert!(
            crate::actors::validate_lookup_path(&path).is_ok(),
            "Path contains illegal characters: {:?}",
            path
        );
        NamedPath {
            system: SystemPath::new(protocol, address, port),
            path,
        }
    }

    /// Construct a new named path from a [socket](std::net::SocketAddr) and vector of path elements
    ///
    /// Make sure that none of the elements of the `path` vector contain slashes (i.e., `/`),
    /// as those will be the path separators for the individual elements later.
    pub fn with_socket(protocol: Transport, socket: SocketAddr, path: Vec<String>) -> NamedPath {
        debug_assert!(
            crate::actors::validate_lookup_path(&path).is_ok(),
            "Path contains illegal characters: {:?}",
            path
        );
        NamedPath {
            system: SystemPath::with_socket(protocol, socket),
            path,
        }
    }

    /// Construct a new named path from a system path and vector of path elements
    ///
    /// Make sure that none of the elements of the `path` vector contain slashes (i.e., `/`),
    /// as those will be the path separators for the individual elements later.
    pub fn with_system(system: SystemPath, path: Vec<String>) -> NamedPath {
        debug_assert!(
            crate::actors::validate_lookup_path(&path).is_ok(),
            "Path contains illegal characters: {:?}",
            path
        );
        NamedPath { system, path }
    }

    /// Returns a reference to the path vector
    pub fn path_ref(&self) -> &[String] {
        &self.path
    }

    /// Returns a copy to the path vector
    pub fn clone_path(&self) -> Vec<String> {
        self.path.clone()
    }

    /// Return just the path vector, discarding the SystemPath
    pub fn into_path(self) -> Vec<String> {
        self.path
    }

    /// Returns a mutable reference to this path's system path part
    pub fn system_mut(&mut self) -> &mut SystemPath {
        &mut self.system
    }

    /// Add the given `segment` to the path, if it is valid to do so
    pub fn push(&mut self, segment: String) -> Result<(), PathParseError> {
        if let Some(last_segment) = self.path.last() {
            validate_lookup_path_segment(last_segment, false)?;
        }
        validate_lookup_path_segment(&segment, true)?;
        self.path.push(segment);
        Ok(())
    }

    /// Add the given relative `path` to this path, if it is valid to do so
    pub fn append(mut self, path: &str) -> Result<Self, PathParseError> {
        if let Some(last_segment) = self.path.last() {
            validate_lookup_path_segment(last_segment, false)?;
        }
        let mut segments = parse_path(path);
        self.path.append(&mut segments);
        validate_lookup_path(&self.path)?;
        Ok(self)
    }
}

impl SystemField for NamedPath {
    fn system(&self) -> &SystemPath {
        &self.system
    }
}

impl TryFrom<String> for NamedPath {
    type Error = PathParseError;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        NamedPath::from_str(&s)
    }
}

impl FromStr for NamedPath {
    type Err = PathParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s1: Vec<&str> = s.split("://").collect();
        if s1.len() != 2 {
            return Err(PathParseError::Form(s.to_string()));
        }
        let proto: Transport = s1[0].parse()?;
        let mut s2: Vec<&str> = s1[1].split(PATH_SEP).collect();
        if s2.is_empty() {
            return Err(PathParseError::Form(s.to_string()));
        }
        let socket = SocketAddr::from_str(s2[0])?;
        let path: Vec<String> = if s2.len() > 1 {
            s2.split_off(1).into_iter().map(|v| v.to_string()).collect()
        } else {
            Vec::default()
        };
        validate_lookup_path(&path)?;
        Ok(NamedPath::with_socket(proto, socket, path))
    }
}

/// Some syntactic sugar for [append](NamedPath::append)
///
/// Allows the pretty `path / "segment" / "*"` syntax
/// to create sub-paths of `path`.
impl Div<&str> for NamedPath {
    type Output = Self;

    fn div(self, rhs: &str) -> Self::Output {
        self.append(rhs).expect("illegal path")
    }
}
/// Some syntactic sugar for [push](NamedPath::push)
///
/// Allows the pretty `path / '*'` syntax
/// to create a broadcast variant of `path`.
impl Div<char> for NamedPath {
    type Output = Self;

    fn div(mut self, rhs: char) -> Self::Output {
        self.push(rhs.to_string()).expect("illegal path");
        self
    }
}

/// Split the `&str` into segments using [PATH_SEP](crate::constants::PATH_SEP)
pub fn parse_path(s: &str) -> Vec<String> {
    s.split(PATH_SEP).map(|v| v.to_string()).collect()
}

/// Check that the given path is valid as a lookup path
///
/// Lookup paths must not contain the following characters:
/// - [PATH_SEP](crate::constants::PATH_SEP)
/// - [UNIQUE_PATH_SEP](crate::constants::UNIQUE_PATH_SEP)
/// - [BROADCAST_MARKER](crate::constants::BROADCAST_MARKER) unless as the only item in the last string
/// - [SELECT_MARKER](crate::constants::SELECT_MARKER) unless as the only item in the last string
pub fn validate_lookup_path(path: &[String]) -> Result<(), PathParseError> {
    let len = path.len();
    for (index, segment) in path.iter().enumerate() {
        validate_lookup_path_segment(segment, (index + 1) == len)?;
    }
    Ok(())
}

/// Check that the given path is valid as an insert path
///
/// Insert paths must not contain the following characters:
/// - [PATH_SEP](crate::constants::PATH_SEP)
/// - [UNIQUE_PATH_SEP](crate::constants::UNIQUE_PATH_SEP)
/// - [BROADCAST_MARKER](crate::constants::BROADCAST_MARKER)
/// - [SELECT_MARKER](crate::constants::SELECT_MARKER)
pub fn validate_insert_path(path: &[String]) -> Result<(), PathParseError> {
    for segment in path.iter() {
        validate_insert_path_segment(segment)?;
    }
    Ok(())
}

pub(crate) fn validate_lookup_path_segment(
    segment: &str,
    is_last: bool,
) -> Result<(), PathParseError> {
    if segment.is_empty() {
        return Err(PathParseError::Form(
            "Path segments may not be empty!".to_string(),
        ));
    }
    let len = segment.len();
    for c in segment.chars() {
        match c {
            PATH_SEP => return Err(PathParseError::IllegalCharacter(PATH_SEP)),
            BROADCAST_MARKER if !is_last && len == 1 => {
                return Err(PathParseError::IllegalCharacter(BROADCAST_MARKER));
            }
            SELECT_MARKER if !is_last && len == 1 => {
                return Err(PathParseError::IllegalCharacter(SELECT_MARKER));
            }
            UNIQUE_PATH_SEP => return Err(PathParseError::IllegalCharacter(UNIQUE_PATH_SEP)),
            _ => (), // ok
        }
    }
    Ok(())
}
pub(crate) fn validate_insert_path_segment(segment: &str) -> Result<(), PathParseError> {
    if segment.is_empty() {
        return Err(PathParseError::Form(
            "Path segments may not be empty!".to_string(),
        ));
    }
    for c in segment.chars() {
        match c {
            PATH_SEP => return Err(PathParseError::IllegalCharacter(PATH_SEP)),
            BROADCAST_MARKER => return Err(PathParseError::IllegalCharacter(BROADCAST_MARKER)),
            SELECT_MARKER => return Err(PathParseError::IllegalCharacter(SELECT_MARKER)),
            UNIQUE_PATH_SEP => return Err(PathParseError::IllegalCharacter(UNIQUE_PATH_SEP)),
            _ => (), // ok
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    const PATH: &str = "local://127.0.0.1:0/test_actor";

    #[test]
    fn actor_path_strings() {
        let ap = ActorPath::from_str(PATH).expect("a proper path");
        println!("Got ActorPath={}", ap);

        let s = ap.to_string();
        assert_eq!(PATH, &s);
        let ap2: ActorPath = s.parse().expect("a proper path");
        assert_eq!(ap, ap2);
    }

    #[test]
    fn actor_path_unique_strings() {
        let ref1 = ActorPath::Unique(UniquePath::new(
            Transport::Local,
            "127.0.0.1".parse().expect("hardcoded IP"),
            8080,
            Uuid::new_v4(),
        ));
        let ref1_string = ref1.to_string();
        let ref1_deser = ActorPath::from_str(&ref1_string).expect("a proper path");
        let ref1_deser2: ActorPath = ref1_string.parse().expect("a proper path");
        assert_eq!(ref1, ref1_deser);
        assert_eq!(ref1, ref1_deser2);
    }

    #[test]
    fn actor_path_named_strings() {
        let ref1 = ActorPath::Named(NamedPath::new(
            Transport::Local,
            "127.0.0.1".parse().expect("hardcoded IP"),
            8080,
            vec!["test".to_string(), "path".to_string()],
        ));
        let ref1_string = ref1.to_string();
        let ref1_deser = ActorPath::from_str(&ref1_string).expect("a proper path");
        let ref1_deser2: ActorPath = ref1_string.parse().expect("a proper path");
        assert_eq!(ref1, ref1_deser);
        assert_eq!(ref1, ref1_deser2);
    }
}