nt_memory/coordination/
namespace.rs

1//! Namespace management for multi-agent systems
2//!
3//! Format: swarm/[agent-id]/[key]
4
5use std::collections::HashSet;
6
7/// Namespace manager
8pub struct Namespace;
9
10impl Namespace {
11    /// Parse namespace
12    pub fn parse(key: &str) -> Result<(String, String, String), String> {
13        let parts: Vec<&str> = key.split('/').collect();
14
15        if parts.len() != 3 {
16            return Err(format!("Invalid namespace format: {}", key));
17        }
18
19        if parts[0] != "swarm" {
20            return Err(format!("Namespace must start with 'swarm': {}", key));
21        }
22
23        Ok((
24            parts[0].to_string(), // "swarm"
25            parts[1].to_string(), // agent_id
26            parts[2].to_string(), // key
27        ))
28    }
29
30    /// Build namespace key
31    pub fn build(agent_id: &str, key: &str) -> String {
32        format!("swarm/{}/{}", agent_id, key)
33    }
34
35    /// Validate namespace
36    pub fn validate(key: &str) -> bool {
37        Self::parse(key).is_ok()
38    }
39
40    /// Extract agent ID from namespace
41    pub fn extract_agent_id(key: &str) -> Option<String> {
42        Self::parse(key).ok().map(|(_, agent_id, _)| agent_id)
43    }
44
45    /// Extract key from namespace
46    pub fn extract_key(key: &str) -> Option<String> {
47        Self::parse(key).ok().map(|(_, _, key)| key)
48    }
49
50    /// Check if key belongs to agent
51    pub fn belongs_to_agent(key: &str, agent_id: &str) -> bool {
52        Self::extract_agent_id(key)
53            .map(|id| id == agent_id)
54            .unwrap_or(false)
55    }
56
57    /// Get all unique agent IDs from keys
58    pub fn get_agent_ids(keys: &[String]) -> HashSet<String> {
59        keys.iter()
60            .filter_map(|k| Self::extract_agent_id(k))
61            .collect()
62    }
63
64    /// Filter keys by agent
65    pub fn filter_by_agent(keys: &[String], agent_id: &str) -> Vec<String> {
66        keys.iter()
67            .filter(|k| Self::belongs_to_agent(k, agent_id))
68            .cloned()
69            .collect()
70    }
71
72    /// Get namespace prefix for agent
73    pub fn agent_prefix(agent_id: &str) -> String {
74        format!("swarm/{}/", agent_id)
75    }
76
77    /// Check if key matches prefix
78    pub fn has_prefix(key: &str, prefix: &str) -> bool {
79        key.starts_with(prefix)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_parse_valid() {
89        let key = "swarm/agent_1/position";
90        let (ns, agent_id, k) = Namespace::parse(key).unwrap();
91
92        assert_eq!(ns, "swarm");
93        assert_eq!(agent_id, "agent_1");
94        assert_eq!(k, "position");
95    }
96
97    #[test]
98    fn test_parse_invalid() {
99        assert!(Namespace::parse("invalid/format").is_err());
100        assert!(Namespace::parse("wrong/agent/key").is_err());
101        assert!(Namespace::parse("swarm/only_two").is_err());
102    }
103
104    #[test]
105    fn test_build() {
106        let key = Namespace::build("agent_1", "position");
107        assert_eq!(key, "swarm/agent_1/position");
108    }
109
110    #[test]
111    fn test_validate() {
112        assert!(Namespace::validate("swarm/agent_1/key"));
113        assert!(!Namespace::validate("invalid/format"));
114    }
115
116    #[test]
117    fn test_extract_agent_id() {
118        let agent_id = Namespace::extract_agent_id("swarm/agent_1/key");
119        assert_eq!(agent_id, Some("agent_1".to_string()));
120
121        let invalid = Namespace::extract_agent_id("invalid");
122        assert_eq!(invalid, None);
123    }
124
125    #[test]
126    fn test_extract_key() {
127        let key = Namespace::extract_key("swarm/agent_1/position");
128        assert_eq!(key, Some("position".to_string()));
129    }
130
131    #[test]
132    fn test_belongs_to_agent() {
133        assert!(Namespace::belongs_to_agent("swarm/agent_1/key", "agent_1"));
134        assert!(!Namespace::belongs_to_agent("swarm/agent_2/key", "agent_1"));
135    }
136
137    #[test]
138    fn test_get_agent_ids() {
139        let keys = vec![
140            "swarm/agent_1/key1".to_string(),
141            "swarm/agent_2/key2".to_string(),
142            "swarm/agent_1/key3".to_string(),
143        ];
144
145        let agent_ids = Namespace::get_agent_ids(&keys);
146        assert_eq!(agent_ids.len(), 2);
147        assert!(agent_ids.contains("agent_1"));
148        assert!(agent_ids.contains("agent_2"));
149    }
150
151    #[test]
152    fn test_filter_by_agent() {
153        let keys = vec![
154            "swarm/agent_1/key1".to_string(),
155            "swarm/agent_2/key2".to_string(),
156            "swarm/agent_1/key3".to_string(),
157        ];
158
159        let filtered = Namespace::filter_by_agent(&keys, "agent_1");
160        assert_eq!(filtered.len(), 2);
161    }
162
163    #[test]
164    fn test_agent_prefix() {
165        let prefix = Namespace::agent_prefix("agent_1");
166        assert_eq!(prefix, "swarm/agent_1/");
167    }
168
169    #[test]
170    fn test_has_prefix() {
171        let key = "swarm/agent_1/position";
172        let prefix = Namespace::agent_prefix("agent_1");
173
174        assert!(Namespace::has_prefix(key, &prefix));
175        assert!(!Namespace::has_prefix(key, "swarm/agent_2/"));
176    }
177}