agentguard_core/
ids.rs

1//! Typed wrappers for Cedar entity identifiers.
2//!
3//! Three separate types (`PrincipalId`, `ActionId`, `ResourceId`) prevent
4//! accidentally passing a principal UID where an action UID is expected.
5//! All wrap a `String` and deref to `str`.
6
7use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::ops::Deref;
10
11/// Identifier of a principal (User or Agent) entity.
12///
13/// Format: a string ID like `alice` (without the type prefix). Combined with
14/// the entity type (`User::` or `Agent::`) it forms a Cedar entity UID.
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(transparent)]
17pub struct PrincipalId(pub String);
18
19impl PrincipalId {
20    pub fn new(s: impl Into<String>) -> Self {
21        Self(s.into())
22    }
23}
24
25impl Deref for PrincipalId {
26    type Target = str;
27    fn deref(&self) -> &str {
28        &self.0
29    }
30}
31
32impl AsRef<str> for PrincipalId {
33    fn as_ref(&self) -> &str {
34        &self.0
35    }
36}
37
38impl fmt::Display for PrincipalId {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str(&self.0)
41    }
42}
43
44impl<T: Into<String>> From<T> for PrincipalId {
45    fn from(value: T) -> Self {
46        Self(value.into())
47    }
48}
49
50/// Identifier of an action entity — e.g. `ToolCall::send_email`.
51#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
52#[serde(transparent)]
53pub struct ActionId(pub String);
54
55impl ActionId {
56    pub fn new(s: impl Into<String>) -> Self {
57        Self(s.into())
58    }
59}
60
61impl Deref for ActionId {
62    type Target = str;
63    fn deref(&self) -> &str {
64        &self.0
65    }
66}
67
68impl AsRef<str> for ActionId {
69    fn as_ref(&self) -> &str {
70        &self.0
71    }
72}
73
74impl fmt::Display for ActionId {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.write_str(&self.0)
77    }
78}
79
80impl<T: Into<String>> From<T> for ActionId {
81    fn from(value: T) -> Self {
82        Self(value.into())
83    }
84}
85
86/// Identifier of a resource entity — e.g. a Mailbox or Repository.
87#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
88#[serde(transparent)]
89pub struct ResourceId(pub String);
90
91impl ResourceId {
92    pub fn new(s: impl Into<String>) -> Self {
93        Self(s.into())
94    }
95}
96
97impl Deref for ResourceId {
98    type Target = str;
99    fn deref(&self) -> &str {
100        &self.0
101    }
102}
103
104impl AsRef<str> for ResourceId {
105    fn as_ref(&self) -> &str {
106        &self.0
107    }
108}
109
110impl fmt::Display for ResourceId {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        f.write_str(&self.0)
113    }
114}
115
116impl<T: Into<String>> From<T> for ResourceId {
117    fn from(value: T) -> Self {
118        Self(value.into())
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn principal_id_derefs_to_str() {
128        let id: PrincipalId = "alice".into();
129        assert_eq!(&*id, "alice");
130        assert_eq!(format!("{}", id), "alice");
131    }
132
133    #[test]
134    fn distinct_types_are_distinct() {
135        let p: PrincipalId = "alice".into();
136        let a: ActionId = "alice".into();
137        let r: ResourceId = "alice".into();
138        // All serialize identically, but are different types — preventing
139        // accidental cross-type usage at compile time.
140        assert_eq!(p.0, a.0);
141        assert_eq!(a.0, r.0);
142    }
143}