lean_ctx/core/trust/
identity.rs1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6macro_rules! identity_id {
7 ($name:ident, $documentation:literal) => {
8 #[doc = $documentation]
9 #[derive(
10 Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize,
11 )]
12 #[serde(transparent)]
13 pub struct $name(String);
14
15 impl $name {
16 #[must_use]
18 pub fn new(value: impl Into<String>) -> Self {
19 Self(value.into())
20 }
21
22 #[must_use]
24 pub fn as_str(&self) -> &str {
25 &self.0
26 }
27
28 #[must_use]
30 pub fn is_empty(&self) -> bool {
31 self.0.is_empty()
32 }
33 }
34
35 impl From<&str> for $name {
36 fn from(value: &str) -> Self {
37 Self::new(value)
38 }
39 }
40
41 impl From<String> for $name {
42 fn from(value: String) -> Self {
43 Self::new(value)
44 }
45 }
46
47 impl AsRef<str> for $name {
48 fn as_ref(&self) -> &str {
49 self.as_str()
50 }
51 }
52
53 impl fmt::Display for $name {
54 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55 formatter.write_str(self.as_str())
56 }
57 }
58 };
59}
60
61identity_id!(
62 TenantId,
63 "Opaque identifier for the tenant that owns an execution or data object."
64);
65identity_id!(
66 ProjectId,
67 "Opaque identifier for a project within a tenant."
68);
69identity_id!(
70 AgentId,
71 "Opaque identifier for an agent execution identity."
72);
73identity_id!(
74 HumanActorId,
75 "Opaque identifier for the human actor responsible for an action."
76);
77identity_id!(
78 ServiceId,
79 "Opaque identifier for a service acting on behalf of a tenant."
80);
81
82#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
84pub enum IdentityScope {
85 #[default]
87 Local,
88 Tenant,
90 CrossTenant,
92}
93
94#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
97pub struct IdentityContext {
98 pub tenant: TenantId,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub project: Option<ProjectId>,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub agent: Option<AgentId>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub human_actor: Option<HumanActorId>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub service: Option<ServiceId>,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub session: Option<String>,
115}
116
117impl IdentityContext {
118 #[must_use]
120 pub fn new(tenant: impl Into<TenantId>) -> Self {
121 Self {
122 tenant: tenant.into(),
123 project: None,
124 agent: None,
125 human_actor: None,
126 service: None,
127 session: None,
128 }
129 }
130
131 #[must_use]
133 pub fn with_project(mut self, project: impl Into<ProjectId>) -> Self {
134 self.project = Some(project.into());
135 self
136 }
137
138 #[must_use]
140 pub fn with_agent(mut self, agent: impl Into<AgentId>) -> Self {
141 self.agent = Some(agent.into());
142 self
143 }
144
145 #[must_use]
147 pub fn with_human_actor(mut self, human_actor: impl Into<HumanActorId>) -> Self {
148 self.human_actor = Some(human_actor.into());
149 self
150 }
151
152 #[must_use]
154 pub fn with_service(mut self, service: impl Into<ServiceId>) -> Self {
155 self.service = Some(service.into());
156 self
157 }
158
159 #[must_use]
161 pub fn with_session(mut self, session: impl Into<String>) -> Self {
162 self.session = Some(session.into());
163 self
164 }
165
166 #[must_use]
168 pub fn tenant_id(&self) -> &TenantId {
169 &self.tenant
170 }
171
172 #[must_use]
174 pub fn scope(&self) -> IdentityScope {
175 if self.tenant.is_empty() {
176 IdentityScope::Local
177 } else {
178 IdentityScope::Tenant
179 }
180 }
181}