Skip to main content

ave_actors_actor/
path.rs

1//! Hierarchical path addressing for actors (e.g. `/user/parent/child`).
2
3use serde::{Deserialize, Serialize};
4
5use std::cmp::Ordering;
6use std::fmt::{Error, Formatter};
7
8/// A slash-separated path that uniquely identifies an actor in the system (e.g. `/user/orders/order-42`).
9///
10/// Paths are built by appending segments with the `/` operator. The first
11/// segment is conventionally the root scope (`user`, `system`, etc.).
12#[derive(
13    Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize,
14)]
15pub struct ActorPath(Vec<String>);
16
17impl ActorPath {
18    /// Returns a path containing only the first segment (the root scope).
19    pub fn root(&self) -> Self {
20        if self.0.len() == 1 {
21            self.clone()
22        } else if !self.0.is_empty() {
23            Self(self.0.iter().take(1).cloned().collect())
24        } else {
25            Self(Vec::new())
26        }
27    }
28
29    /// Returns this path with its last segment removed, or an empty path if already at root.
30    pub fn parent(&self) -> Self {
31        if self.0.len() > 1 {
32            let mut tokens = self.0.clone();
33            tokens.truncate(tokens.len() - 1);
34            Self(tokens)
35        } else {
36            Self(Vec::new())
37        }
38    }
39
40    /// Returns the last segment of the path (the actor's local id).
41    pub fn key(&self) -> String {
42        self.0.last().cloned().unwrap_or_else(|| "".to_string())
43    }
44
45    /// Returns the number of segments in this path (its depth).
46    pub const fn level(&self) -> usize {
47        self.0.len()
48    }
49
50    /// Returns this path truncated to `level` segments, or `self` unchanged if `level` is out of range.
51    pub fn at_level(&self, level: usize) -> Self {
52        if level < 1 || level >= self.level() {
53            self.clone()
54        } else if self.is_top_level() {
55            self.root()
56        } else if level == self.level() - 1 {
57            self.parent()
58        } else {
59            let mut tokens = self.0.clone();
60            tokens.truncate(level);
61            Self(tokens)
62        }
63    }
64
65    /// Returns `true` if this path has no segments (e.g. parsed from `"/"`).
66    pub const fn is_empty(&self) -> bool {
67        self.0.is_empty()
68    }
69
70    /// Returns `true` if this path is a proper prefix of `other`, meaning this actor is an ancestor of `other`.
71    pub fn is_ancestor_of(&self, other: &Self) -> bool {
72        self.0.len() < other.0.len() && other.0.starts_with(&self.0)
73    }
74
75    /// Returns `true` if `other` is a proper prefix of this path, meaning this actor is a descendant of `other`.
76    pub fn is_descendant_of(&self, other: &Self) -> bool {
77        other.0.len() < self.0.len() && self.0.starts_with(&other.0)
78    }
79
80    /// Returns `true` if this path is the direct parent of `other` (one level above it).
81    pub fn is_parent_of(&self, other: &Self) -> bool {
82        *self == other.parent()
83    }
84
85    /// Returns `true` if `other` is the direct parent of this path (this path is one level below `other`).
86    pub fn is_child_of(&self, other: &Self) -> bool {
87        self.parent() == *other
88    }
89
90    /// Returns `true` if this path has exactly one segment, i.e. it is a direct child of the root scope.
91    pub const fn is_top_level(&self) -> bool {
92        self.0.len() == 1
93    }
94}
95
96/// Creates a path from a `/`-separated string such as `"/user/parent/child"`.
97impl From<&str> for ActorPath {
98    fn from(str: &str) -> Self {
99        let tokens: Vec<String> = str
100            .split('/')
101            .filter(|x| !x.trim().is_empty())
102            .map(|s| s.to_string())
103            .collect();
104        Self(tokens)
105    }
106}
107
108/// Creates a path from a string. Equivalent to [`ActorPath::from`] with a `&str`.
109impl From<String> for ActorPath {
110    fn from(string: String) -> Self {
111        Self::from(string.as_str())
112    }
113}
114
115impl From<&String> for ActorPath {
116    fn from(string: &String) -> Self {
117        Self::from(string.as_str())
118    }
119}
120
121/// Appends `segment` as a new path component: `parent_path / "child-id"`.
122impl std::ops::Div<&str> for ActorPath {
123    type Output = Self;
124
125    fn div(self, rhs: &str) -> Self::Output {
126        let mut keys = self.0;
127        let mut tokens: Vec<String> = rhs
128            .split('/')
129            .filter(|x| !x.trim().is_empty())
130            .map(|s| s.to_string())
131            .collect();
132
133        keys.append(&mut tokens);
134        Self(keys)
135    }
136}
137
138impl std::fmt::Display for ActorPath {
139    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
140        match self.level().cmp(&1) {
141            Ordering::Less => write!(f, "/"),
142            Ordering::Equal => write!(f, "/{}", self.0[0]),
143            Ordering::Greater => write!(f, "/{}", self.0.join("/")),
144        }
145    }
146}
147
148impl std::fmt::Debug for ActorPath {
149    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
150        match self.level().cmp(&1) {
151            Ordering::Less => write!(f, "/"),
152            Ordering::Equal => write!(f, "/{}", self.0[0]),
153            Ordering::Greater => write!(f, "/{}", self.0.join("/")),
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160
161    use super::*;
162
163    #[test]
164    fn parse_empty_string() {
165        let path = ActorPath::from("");
166        assert_eq!(path.0, Vec::<String>::new());
167    }
168
169    #[test]
170    fn parse_single_root() {
171        let path = ActorPath::from("/acme");
172        println!("{:?}", path);
173        assert_eq!(path.0, vec!["acme"]);
174    }
175
176    #[test]
177    fn parse_two_deep() {
178        let path = ActorPath::from("/acme/building");
179        println!("{:?}", path);
180        assert_eq!(path.0, vec!["acme", "building"]);
181    }
182
183    #[test]
184    fn parse_three_deep() {
185        let path = ActorPath::from("/acme/building/room");
186        println!("{:?}", path);
187        assert_eq!(path.0, vec!["acme", "building", "room"]);
188    }
189
190    #[test]
191    fn parse_levels() {
192        let path = ActorPath::from("/acme/building/room/sensor");
193        println!("{:?}", path);
194        assert_eq!(path.level(), 4);
195    }
196
197    #[test]
198    fn test_get_key() {
199        let path = ActorPath::from("/acme/building/room/sensor");
200        println!("{:?}", path);
201        assert_eq!(path.key(), "sensor".to_string());
202    }
203
204    #[test]
205    fn parse_get_parent() {
206        let path = ActorPath::from("/acme/building/room/sensor").parent();
207        println!("{:?}", path);
208        assert_eq!(path.parent().0, vec!["acme", "building"]);
209    }
210
211    #[test]
212    fn parse_to_string() {
213        let path = ActorPath::from("/acme/building/room/sensor");
214        let string = path.to_string();
215        println!("{:?}", string);
216        assert_eq!(string, "/acme/building/room/sensor");
217    }
218
219    #[test]
220    fn parse_root_at_root() {
221        let path = ActorPath::from("/acme");
222        let string = path.root().to_string();
223        println!("{:?}", string);
224        assert_eq!(string, "/acme");
225    }
226
227    #[test]
228    fn parse_parent_at_root() {
229        let path = ActorPath::from("/acme");
230        let string = path.parent().to_string();
231        println!("{:?}", string);
232        assert_eq!(string, "/");
233    }
234
235    #[test]
236    fn parse_root_to_string() {
237        let path = ActorPath::from("/acme/building/room/sensor");
238        let string = path.root().to_string();
239        println!("{:?}", string);
240        assert_eq!(string, "/acme");
241    }
242
243    #[test]
244    fn test_if_empty() {
245        let path = ActorPath::from("/");
246        assert!(path.is_empty());
247        let not_empty = ActorPath::from("/not_empty");
248        assert!(!not_empty.is_empty());
249    }
250
251    #[test]
252    fn test_if_parent_child() {
253        let path = ActorPath::from("/acme/building/room/sensor");
254        let parent = path.parent();
255        assert!(parent.is_parent_of(&path));
256        assert!(path.is_child_of(&parent));
257    }
258
259    #[test]
260    fn test_if_descendant() {
261        let path = ActorPath::from("/acme/building/room/sensor");
262        let parent = path.parent();
263        assert!(path.is_descendant_of(&parent));
264        assert!(!path.is_descendant_of(&path));
265    }
266
267    #[test]
268    fn test_if_ancestor() {
269        let path = ActorPath::from("/acme/building/room/sensor");
270        let parent = path.parent();
271        assert!(parent.is_ancestor_of(&path));
272        assert!(!path.is_ancestor_of(&path));
273    }
274
275    #[test]
276    fn test_if_ancestor_descendant() {
277        let path = ActorPath::from("/acme/building/room/sensor");
278        let root = path.root();
279        assert!(root.is_ancestor_of(&path));
280        assert!(path.is_descendant_of(&root));
281    }
282
283    #[test]
284    fn test_root_slash_relationships() {
285        let root = ActorPath::from("/");
286        let child = ActorPath::from("/acme");
287        let grandchild = ActorPath::from("/acme/building");
288
289        assert!(root.is_ancestor_of(&child));
290        assert!(root.is_ancestor_of(&grandchild));
291        assert!(child.is_descendant_of(&root));
292        assert!(grandchild.is_descendant_of(&root));
293        assert!(!root.is_ancestor_of(&root));
294        assert!(!root.is_descendant_of(&root));
295    }
296
297    #[test]
298    fn test_if_root() {
299        let path = ActorPath::from("/acme/building/room/sensor");
300        let root = path.root();
301        println!("{:?}", path);
302        println!("{:?}", root);
303        assert!(root.is_top_level());
304        assert!(!path.is_top_level());
305    }
306
307    #[test]
308    fn test_at_level() {
309        let path = ActorPath::from("/acme/building/room/sensor");
310        assert_eq!(path.at_level(0), path);
311        assert_eq!(path.at_level(1), path.root());
312        assert_eq!(path.at_level(2), ActorPath::from("/acme/building"));
313        assert_eq!(path.at_level(3), path.parent());
314        assert_eq!(path.at_level(4), path);
315        assert_eq!(path.at_level(5), path);
316    }
317
318    #[test]
319    fn test_add_path() {
320        let path = ActorPath::from("/acme");
321        let child = path.clone() / "child";
322        println!("{}", &child);
323        assert!(path.is_parent_of(&child))
324    }
325}