enact_core/context/
tenant.rs1use crate::kernel::{TenantId, UserId};
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ResourceLimits {
24 pub max_steps: u32,
26 pub max_tokens: u32,
28 pub max_wall_time_ms: u64,
30 pub max_memory_mb: Option<u32>,
32 pub max_concurrent_executions: Option<u32>,
34}
35
36impl Default for ResourceLimits {
37 fn default() -> Self {
38 Self {
39 max_steps: 100,
40 max_tokens: 100_000,
41 max_wall_time_ms: 300_000, max_memory_mb: None,
43 max_concurrent_executions: None,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TenantContext {
54 pub tenant_id: TenantId,
56
57 pub user_id: Option<UserId>,
59
60 pub limits: ResourceLimits,
62
63 pub features: HashMap<String, bool>,
65
66 pub metadata: HashMap<String, serde_json::Value>,
68}
69
70impl TenantContext {
71 pub fn new(tenant_id: TenantId) -> Self {
73 Self {
74 tenant_id,
75 user_id: None,
76 limits: ResourceLimits::default(),
77 features: HashMap::new(),
78 metadata: HashMap::new(),
79 }
80 }
81
82 pub fn with_user(mut self, user_id: UserId) -> Self {
84 self.user_id = Some(user_id);
85 self
86 }
87
88 pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
90 self.limits = limits;
91 self
92 }
93
94 pub fn with_feature(mut self, feature: impl Into<String>, enabled: bool) -> Self {
96 self.features.insert(feature.into(), enabled);
97 self
98 }
99
100 pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
102 self.metadata.insert(key.into(), value);
103 self
104 }
105
106 pub fn is_feature_enabled(&self, feature: &str) -> bool {
108 self.features.get(feature).copied().unwrap_or(false)
109 }
110
111 pub fn tenant_id(&self) -> &TenantId {
113 &self.tenant_id
114 }
115
116 pub fn user_id(&self) -> Option<&UserId> {
118 self.user_id.as_ref()
119 }
120
121 pub fn child_context(&self, user_id: Option<UserId>) -> Self {
124 Self {
125 tenant_id: self.tenant_id.clone(),
126 user_id: user_id.or_else(|| self.user_id.clone()),
127 limits: self.limits.clone(),
128 features: self.features.clone(),
129 metadata: HashMap::new(), }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_tenant_context_required_id() {
140 let tenant = TenantContext::new(TenantId::from("tenant_123"));
141 assert_eq!(tenant.tenant_id().as_str(), "tenant_123");
142 }
143
144 #[test]
145 fn test_tenant_context_with_user() {
146 let tenant =
147 TenantContext::new(TenantId::from("tenant_123")).with_user(UserId::from("usr_456"));
148
149 assert_eq!(tenant.user_id().unwrap().as_str(), "usr_456");
150 }
151
152 #[test]
153 fn test_feature_flags() {
154 let tenant = TenantContext::new(TenantId::from("tenant_123"))
155 .with_feature("beta_tools", true)
156 .with_feature("experimental", false);
157
158 assert!(tenant.is_feature_enabled("beta_tools"));
159 assert!(!tenant.is_feature_enabled("experimental"));
160 assert!(!tenant.is_feature_enabled("nonexistent"));
161 }
162
163 #[test]
164 fn test_child_context() {
165 let parent = TenantContext::new(TenantId::from("tenant_123"))
166 .with_user(UserId::from("usr_456"))
167 .with_feature("beta", true);
168
169 let child = parent.child_context(Some(UserId::from("usr_789")));
170
171 assert_eq!(child.tenant_id().as_str(), "tenant_123");
172 assert_eq!(child.user_id().unwrap().as_str(), "usr_789");
173 assert!(child.is_feature_enabled("beta"));
174 }
175}