1use crate::errors::PathParseError;
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15#[repr(transparent)]
16pub struct AddrHash(pub(crate) u128);
17
18impl AddrHash {
19 pub fn new(path: &ActorPath, generation: u64) -> Self {
21 let digest = blake3::hash(path.as_str().as_bytes());
22 let path_hash = u64::from_le_bytes(digest.as_bytes()[..8].try_into().unwrap());
23 Self::from_parts(path_hash, generation)
24 }
25
26 pub fn from_parts(path_hash: u64, generation: u64) -> Self {
28 let val = (u128::from(path_hash) << 64) | u128::from(generation);
29 Self(val)
30 }
31
32 pub fn from_path(path: &ActorPath) -> Self {
35 Self::new(path, 0)
36 }
37
38 pub fn synthetic(bytes: &[u8]) -> Self {
41 let digest = blake3::hash(bytes);
42 let path_hash = u64::from_le_bytes(digest.as_bytes()[..8].try_into().unwrap());
43 Self::from_parts(path_hash, 0)
44 }
45
46 pub fn path_hash(self) -> u64 {
47 (self.0 >> 64) as u64
48 }
49
50 pub fn generation(self) -> u64 {
51 (self.0 & 0xFFFFFFFF_FFFFFFFF) as u64
52 }
53
54 pub fn as_u128(self) -> u128 {
55 self.0
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
67pub struct ActorPath(pub(crate) String);
68
69impl ActorPath {
70 pub fn root() -> Self {
71 Self("/".to_string())
72 }
73
74 pub fn parse(s: &str) -> Result<Self, PathParseError> {
75 if s.is_empty() {
76 return Err(PathParseError::Empty);
77 }
78 if !s.starts_with('/') {
79 return Err(PathParseError::MustStartWithSlash);
80 }
81 if s.len() > 1 && s.ends_with('/') {
82 return Err(PathParseError::TrailingSlash);
83 }
84 if s == "/" {
85 return Ok(Self::root());
86 }
87 for seg in s.split('/').skip(1) {
88 if seg.is_empty() {
89 return Err(PathParseError::EmptySegment);
90 }
91 if !seg
92 .chars()
93 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | ':' | '.'))
94 {
95 return Err(PathParseError::InvalidSegment);
96 }
97 }
98 Ok(Self(s.to_owned()))
99 }
100
101 pub fn child(&self, name: &str) -> Result<Self, PathParseError> {
102 if name.is_empty() || name.contains('/') {
103 return Err(PathParseError::InvalidSegment);
104 }
105 if !name
106 .chars()
107 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | ':' | '.'))
108 {
109 return Err(PathParseError::InvalidSegment);
110 }
111 let next = if self.0 == "/" {
112 format!("/{name}")
113 } else {
114 format!("{}/{name}", self.0)
115 };
116 Self::parse(&next)
117 }
118
119 pub fn parent(&self) -> Option<Self> {
120 if self.0 == "/" {
121 return None;
122 }
123 let idx = self.0.rfind('/').expect("ActorPath always contains '/'");
124 if idx == 0 {
125 Some(Self::root())
126 } else {
127 Some(Self(self.0[..idx].to_owned()))
128 }
129 }
130
131 pub fn is_ancestor_of(&self, other: &Self) -> bool {
132 if self.0 == "/" {
133 return other.0 != "/";
134 }
135 if self.0 == other.0 {
136 return false;
137 }
138 let prefix = format!("{}/", self.0);
139 other.0.starts_with(&prefix)
140 }
141
142 pub fn as_str(&self) -> &str {
143 &self.0
144 }
145}
146
147impl fmt::Display for ActorPath {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "{}", self.0)
150 }
151}