1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(transparent)]
8pub struct TenantId(pub Uuid);
9
10impl TenantId {
11 pub fn new() -> Self {
12 Self(Uuid::new_v4())
13 }
14}
15
16impl Default for TenantId {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl std::fmt::Display for TenantId {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "{}", self.0)
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Tenant {
31 pub id: TenantId,
32 pub name: String,
33 pub api_key: String,
34 pub status: TenantStatus,
35 pub quota: TenantQuota,
36 pub created_at: DateTime<Utc>,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum TenantStatus {
43 Active,
44 Suspended,
45 Trial,
46}
47
48impl std::fmt::Display for TenantStatus {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Self::Active => write!(f, "active"),
52 Self::Suspended => write!(f, "suspended"),
53 Self::Trial => write!(f, "trial"),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct TenantQuota {
61 pub max_fighters: usize,
63 pub max_gorillas: usize,
65 pub max_bouts: usize,
67 pub max_tokens_per_day: u64,
69 #[serde(default)]
71 pub max_tools: Vec<String>,
72}
73
74impl Default for TenantQuota {
75 fn default() -> Self {
76 Self {
77 max_fighters: 10,
78 max_gorillas: 5,
79 max_bouts: 50,
80 max_tokens_per_day: 1_000_000,
81 max_tools: Vec::new(),
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_tenant_id_display() {
92 let uuid = Uuid::nil();
93 let id = TenantId(uuid);
94 assert_eq!(id.to_string(), uuid.to_string());
95 }
96
97 #[test]
98 fn test_tenant_id_new_is_unique() {
99 let id1 = TenantId::new();
100 let id2 = TenantId::new();
101 assert_ne!(id1, id2);
102 }
103
104 #[test]
105 fn test_tenant_id_default() {
106 let id = TenantId::default();
107 assert_ne!(id.0, Uuid::nil());
108 }
109
110 #[test]
111 fn test_tenant_id_serde_transparent() {
112 let uuid = Uuid::new_v4();
113 let id = TenantId(uuid);
114 let json = serde_json::to_string(&id).expect("serialize");
115 assert_eq!(json, format!("\"{}\"", uuid));
116 let deser: TenantId = serde_json::from_str(&json).expect("deserialize");
117 assert_eq!(deser, id);
118 }
119
120 #[test]
121 fn test_tenant_id_copy_clone() {
122 let id = TenantId::new();
123 let copied = id;
124 let cloned = id.clone();
125 assert_eq!(id, copied);
126 assert_eq!(id, cloned);
127 }
128
129 #[test]
130 fn test_tenant_id_hash() {
131 let id = TenantId::new();
132 let mut set = std::collections::HashSet::new();
133 set.insert(id);
134 set.insert(id);
135 assert_eq!(set.len(), 1);
136 }
137
138 #[test]
139 fn test_tenant_status_display() {
140 assert_eq!(TenantStatus::Active.to_string(), "active");
141 assert_eq!(TenantStatus::Suspended.to_string(), "suspended");
142 assert_eq!(TenantStatus::Trial.to_string(), "trial");
143 }
144
145 #[test]
146 fn test_tenant_status_serde_roundtrip() {
147 let statuses = vec![
148 TenantStatus::Active,
149 TenantStatus::Suspended,
150 TenantStatus::Trial,
151 ];
152 for status in &statuses {
153 let json = serde_json::to_string(status).expect("serialize");
154 let deser: TenantStatus = serde_json::from_str(&json).expect("deserialize");
155 assert_eq!(&deser, status);
156 }
157 }
158
159 #[test]
160 fn test_tenant_quota_default() {
161 let quota = TenantQuota::default();
162 assert_eq!(quota.max_fighters, 10);
163 assert_eq!(quota.max_gorillas, 5);
164 assert_eq!(quota.max_bouts, 50);
165 assert_eq!(quota.max_tokens_per_day, 1_000_000);
166 assert!(quota.max_tools.is_empty());
167 }
168
169 #[test]
170 fn test_tenant_quota_serde_roundtrip() {
171 let quota = TenantQuota {
172 max_fighters: 20,
173 max_gorillas: 10,
174 max_bouts: 100,
175 max_tokens_per_day: 5_000_000,
176 max_tools: vec!["read_file".to_string(), "web_fetch".to_string()],
177 };
178 let json = serde_json::to_string("a).expect("serialize");
179 let deser: TenantQuota = serde_json::from_str(&json).expect("deserialize");
180 assert_eq!(deser.max_fighters, 20);
181 assert_eq!(deser.max_tools.len(), 2);
182 }
183
184 #[test]
185 fn test_tenant_serde_roundtrip() {
186 let tenant = Tenant {
187 id: TenantId::new(),
188 name: "Acme Corp".to_string(),
189 api_key: "pk_test_abc123".to_string(),
190 status: TenantStatus::Active,
191 quota: TenantQuota::default(),
192 created_at: chrono::Utc::now(),
193 };
194 let json = serde_json::to_string(&tenant).expect("serialize");
195 let deser: Tenant = serde_json::from_str(&json).expect("deserialize");
196 assert_eq!(deser.id, tenant.id);
197 assert_eq!(deser.name, "Acme Corp");
198 assert_eq!(deser.api_key, "pk_test_abc123");
199 assert_eq!(deser.status, TenantStatus::Active);
200 }
201}