forked from nulang-org/nulang
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathregistry.rs
More file actions
211 lines (182 loc) · 5.95 KB
/
Copy pathregistry.rs
File metadata and controls
211 lines (182 loc) · 5.95 KB
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
//! Local actor name registry: register/unregister/whereis/registered.
//!
//! Per-node local registry, similar to Erlang's local process registry.
//! Cluster-wide naming is handled by virtual actors.
use std::collections::HashMap;
use std::sync::RwLock;
/// Errors that can occur during registry operations.
#[derive(Debug, Clone, PartialEq)]
pub enum RegisterError {
NameAlreadyRegistered(String),
InvalidName(String),
ActorNotFound(u64),
}
impl std::fmt::Display for RegisterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RegisterError::NameAlreadyRegistered(name) => {
write!(f, "Name '{}' is already registered", name)
}
RegisterError::InvalidName(name) => {
write!(f, "Invalid actor name: '{}'", name)
}
RegisterError::ActorNotFound(id) => {
write!(f, "Actor {} not found", id)
}
}
}
}
impl std::error::Error for RegisterError {}
/// Local actor name registry.
///
/// Maps human-readable names to actor IDs. Each node has its own registry.
/// Names are unique within a node. Registration fails if the name is already
/// in use.
pub struct ActorRegistry {
maps: RwLock<RegistryMaps>,
}
struct RegistryMaps {
names: HashMap<String, u64>,
reverse: HashMap<u64, String>,
}
impl ActorRegistry {
/// Create a new empty registry.
pub fn new() -> Self {
ActorRegistry {
maps: RwLock::new(RegistryMaps {
names: HashMap::new(),
reverse: HashMap::new(),
}),
}
}
/// Register a name for an actor.
pub fn register(&self, name: &str, actor_id: u64) -> Result<(), RegisterError> {
Self::validate_name(name)?;
let mut maps = self
.maps
.write()
.map_err(|_| RegisterError::InvalidName(name.to_string()))?;
if maps.names.contains_key(name) {
return Err(RegisterError::NameAlreadyRegistered(name.to_string()));
}
maps.names.insert(name.to_string(), actor_id);
maps.reverse.insert(actor_id, name.to_string());
Ok(())
}
/// Unregister a name.
pub fn unregister(&self, name: &str) -> Result<(), RegisterError> {
let mut maps = self
.maps
.write()
.map_err(|_| RegisterError::InvalidName(name.to_string()))?;
let actor_id = maps
.names
.remove(name)
.ok_or_else(|| RegisterError::InvalidName(name.to_string()))?;
maps.reverse.remove(&actor_id);
Ok(())
}
/// Look up an actor ID by name.
pub fn whereis(&self, name: &str) -> Option<u64> {
let maps = self.maps.read().ok()?;
maps.names.get(name).copied()
}
/// List all registered names.
pub fn registered(&self) -> Vec<String> {
let maps = match self.maps.read() {
Ok(m) => m,
Err(_) => return Vec::new(),
};
maps.names.keys().cloned().collect()
}
/// Remove all names for a given actor (called on actor exit).
pub fn unregister_by_actor(&self, actor_id: u64) -> Vec<String> {
let mut maps = match self.maps.write() {
Ok(m) => m,
Err(_) => return Vec::new(),
};
let mut removed = Vec::new();
if let Some(name) = maps.reverse.get(&actor_id) {
removed.push(name.clone());
}
for name in &removed {
maps.names.remove(name);
}
maps.reverse.remove(&actor_id);
removed
}
/// Check if a name is registered.
pub fn is_registered(&self, name: &str) -> bool {
let maps = match self.maps.read() {
Ok(m) => m,
Err(_) => return false,
};
maps.names.contains_key(name)
}
fn validate_name(name: &str) -> Result<(), RegisterError> {
if name.is_empty() {
return Err(RegisterError::InvalidName("(empty)".to_string()));
}
if name.chars().any(|c| c.is_whitespace()) {
return Err(RegisterError::InvalidName(name.to_string()));
}
if name.chars().next().unwrap().is_ascii_digit() {
return Err(RegisterError::InvalidName(name.to_string()));
}
if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err(RegisterError::InvalidName(name.to_string()));
}
Ok(())
}
}
impl Default for ActorRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_and_whereis() {
let reg = ActorRegistry::new();
reg.register("my_actor", 42).unwrap();
assert_eq!(reg.whereis("my_actor"), Some(42));
}
#[test]
fn test_unregister() {
let reg = ActorRegistry::new();
reg.register("temp", 42).unwrap();
reg.unregister("temp").unwrap();
assert_eq!(reg.whereis("temp"), None);
}
#[test]
fn test_duplicate_name_fails() {
let reg = ActorRegistry::new();
reg.register("shared", 1).unwrap();
assert!(reg.register("shared", 2).is_err());
}
#[test]
fn test_invalid_names() {
let reg = ActorRegistry::new();
assert!(reg.register("", 1).is_err());
assert!(reg.register("has space", 1).is_err());
assert!(reg.register("1digit", 1).is_err());
}
#[test]
fn test_unregister_by_actor() {
let reg = ActorRegistry::new();
reg.register("dies_soon", 42).unwrap();
let removed = reg.unregister_by_actor(42);
assert!(removed.contains(&"dies_soon".to_string()));
assert_eq!(reg.whereis("dies_soon"), None);
}
#[test]
fn test_registered_list() {
let reg = ActorRegistry::new();
reg.register("first", 1).unwrap();
reg.register("second", 2).unwrap();
let names = reg.registered();
assert_eq!(names.len(), 2);
}
}