Skip to main content

solti_model/domain/identity/
agent.rs

1//! # Agent identifier.
2//!
3//! [`AgentId`] identifies an agent instance in multi-agent deployments.
4
5use super::validate_identity;
6use crate::error::ModelError;
7
8/// Maximum length of an `AgentId`.
9pub const AGENT_ID_MAX_LEN: usize = 128;
10
11arc_str_newtype! {
12    /// Unique identifier for a solti agent instance.
13    ///
14    /// Represents the identity of a running agent process.
15    /// The caller is responsible for providing a meaningful ID (e.g. UUID, hostname, pod name).
16    ///
17    /// ```rust
18    /// use solti_model::AgentId;
19    ///
20    /// // From a UUID
21    /// let id = AgentId::new("550e8400-e29b-41d4-a716-446655440000");
22    /// assert_eq!(id.as_str(), "550e8400-e29b-41d4-a716-446655440000");
23    ///
24    /// // From a Kubernetes pod name
25    /// let id: AgentId = "worker-pod-7b9f4".into();
26    /// assert_eq!(format!("{id}"), "worker-pod-7b9f4");
27    /// ```
28    pub struct AgentId;
29}
30
31impl AgentId {
32    /// Validate that the agent id is safe to use across the SDK and the wire protocol.
33    ///
34    /// See [`validate_identity`] for the exact rules.
35    pub fn validate_format(&self) -> Result<(), ModelError> {
36        validate_identity("agent_id", self.as_str(), AGENT_ID_MAX_LEN)
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use std::sync::Arc;
44
45    #[test]
46    fn agent_id_from_string() {
47        let id = AgentId::from("my-agent-001");
48        assert_eq!(id.as_str(), "my-agent-001");
49    }
50
51    #[test]
52    fn agent_id_display() {
53        let id = AgentId::new("worker-pod-7b9f4");
54        assert_eq!(format!("{}", id), "worker-pod-7b9f4");
55    }
56
57    #[test]
58    fn agent_id_serde_transparent() {
59        let id = AgentId::from("550e8400-e29b-41d4-a716-446655440000");
60        let json = serde_json::to_string(&id).unwrap();
61        assert_eq!(json, r#""550e8400-e29b-41d4-a716-446655440000""#);
62
63        let back: AgentId = serde_json::from_str(&json).unwrap();
64        assert_eq!(back, id);
65    }
66
67    #[test]
68    fn agent_id_hash_equality() {
69        use std::collections::HashSet;
70
71        let mut set = HashSet::new();
72        set.insert(AgentId::from("agent-a"));
73        set.insert(AgentId::from("agent-b"));
74        set.insert(AgentId::from("agent-a"));
75
76        assert_eq!(set.len(), 2);
77        assert!(set.contains(&AgentId::from("agent-a")));
78    }
79
80    #[test]
81    fn clone_is_cheap() {
82        let id = AgentId::new("shared-agent");
83        let cloned = id.clone();
84        let a: Arc<str> = id.into_inner();
85        let b: Arc<str> = cloned.into_inner();
86        assert!(Arc::ptr_eq(&a, &b));
87    }
88
89    #[test]
90    fn partial_eq_with_str() {
91        let id = AgentId::new("test-agent");
92        assert_eq!(id, *"test-agent");
93    }
94
95    #[test]
96    fn into_inner() {
97        let id = AgentId::new("owned");
98        let s: Arc<str> = id.into_inner();
99        assert_eq!(&*s, "owned");
100    }
101
102    #[test]
103    fn validate_format_accepts_valid() {
104        AgentId::new("550e8400-e29b-41d4-a716-446655440000")
105            .validate_format()
106            .unwrap();
107        AgentId::new("worker-pod-7b9f4").validate_format().unwrap();
108        AgentId::new("agent.eu-west-1.01")
109            .validate_format()
110            .unwrap();
111    }
112
113    #[test]
114    fn validate_format_rejects_invalid() {
115        assert!(AgentId::new("").validate_format().is_err());
116        assert!(AgentId::new("agent with space").validate_format().is_err());
117        assert!(AgentId::new("agent/path").validate_format().is_err());
118    }
119}