1use serde::{Deserialize, Serialize};
6use std::fmt;
7use thiserror::Error;
8use uuid::Uuid;
9
10pub const MAX_IDENTITY_BYTES: usize = 256;
12
13#[derive(Clone, Debug, Error, PartialEq, Eq)]
15pub enum IdentityError {
16 #[error("identity must be non-empty")]
18 Empty,
19 #[error("identity exceeds maximum length of {MAX_IDENTITY_BYTES} bytes")]
21 TooLong,
22 #[error("identity must not contain control characters")]
24 ControlCharacter,
25}
26
27pub fn validate_identity_string(value: &str) -> Result<(), IdentityError> {
29 if value.is_empty() {
30 return Err(IdentityError::Empty);
31 }
32 if value.len() > MAX_IDENTITY_BYTES {
33 return Err(IdentityError::TooLong);
34 }
35 if value.chars().any(|c| c.is_control()) {
36 return Err(IdentityError::ControlCharacter);
37 }
38 Ok(())
39}
40
41fn validated_string(value: impl Into<String>) -> Result<String, IdentityError> {
42 let s = value.into();
43 validate_identity_string(&s)?;
44 Ok(s)
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
49pub struct ConnectionId(String);
50
51impl ConnectionId {
52 pub fn new(value: impl Into<String>) -> Self {
58 Self::try_new(value).expect("ConnectionId::new requires a valid identity string")
59 }
60
61 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
63 Ok(Self(validated_string(value)?))
64 }
65
66 pub fn generate() -> Self {
68 Self(Uuid::new_v4().to_string())
69 }
70
71 pub fn as_str(&self) -> &str {
73 &self.0
74 }
75}
76
77impl fmt::Display for ConnectionId {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.write_str(&self.0)
80 }
81}
82
83#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub struct MonoloopRunId(String);
86
87impl MonoloopRunId {
88 pub fn new(value: impl Into<String>) -> Self {
94 Self::try_new(value).expect("MonoloopRunId::new requires a valid identity string")
95 }
96
97 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
99 Ok(Self(validated_string(value)?))
100 }
101
102 pub fn generate() -> Self {
104 Self(Uuid::new_v4().to_string())
105 }
106
107 pub fn from_transaction(id: &TransactionId) -> Self {
109 Self(format!("txn:{}", id.as_uuid()))
110 }
111
112 pub fn as_str(&self) -> &str {
114 &self.0
115 }
116}
117
118impl Default for MonoloopRunId {
119 fn default() -> Self {
120 Self::generate()
121 }
122}
123
124impl fmt::Display for MonoloopRunId {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 f.write_str(&self.0)
127 }
128}
129
130#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
135pub struct ExternalSessionId(String);
136
137impl ExternalSessionId {
138 pub fn new(value: impl Into<String>) -> Self {
144 Self::try_new(value).expect("ExternalSessionId::new requires a valid identity string")
145 }
146
147 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
149 Ok(Self(validated_string(value)?))
150 }
151
152 pub fn as_str(&self) -> &str {
154 &self.0
155 }
156}
157
158impl fmt::Display for ExternalSessionId {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 f.write_str("<external-session>")
162 }
163}
164
165#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
168pub struct GrokSessionId(ExternalSessionId);
169
170impl GrokSessionId {
171 pub fn new(value: impl Into<String>) -> Self {
177 Self::try_new(value).expect("GrokSessionId::new requires a valid identity string")
178 }
179
180 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
182 Ok(Self(ExternalSessionId::try_new(value)?))
183 }
184
185 pub fn as_str(&self) -> &str {
187 self.0.as_str()
188 }
189
190 pub fn as_external(&self) -> &ExternalSessionId {
192 &self.0
193 }
194
195 pub fn into_external(self) -> ExternalSessionId {
197 self.0
198 }
199}
200
201impl From<GrokSessionId> for ExternalSessionId {
202 fn from(value: GrokSessionId) -> Self {
203 value.0
204 }
205}
206
207#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
209pub struct RequestId(String);
210
211impl RequestId {
212 pub fn new(value: impl Into<String>) -> Self {
218 Self::try_new(value).expect("RequestId::new requires a valid identity string")
219 }
220
221 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
223 Ok(Self(validated_string(value)?))
224 }
225
226 pub fn generate() -> Self {
228 Self(Uuid::new_v4().to_string())
229 }
230
231 pub fn as_str(&self) -> &str {
233 &self.0
234 }
235}
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
239pub struct TransactionId(Uuid);
240
241impl TransactionId {
242 pub fn generate() -> Self {
244 Self(Uuid::new_v4())
245 }
246
247 pub fn from_uuid(id: Uuid) -> Self {
249 Self(id)
250 }
251
252 pub fn as_uuid(&self) -> Uuid {
254 self.0
255 }
256
257 pub fn as_str(&self) -> String {
259 self.0.to_string()
260 }
261}
262
263impl fmt::Display for TransactionId {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(f, "{}", self.0)
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
271pub struct ExchangeId(Uuid);
272
273impl ExchangeId {
274 pub fn generate() -> Self {
276 Self(Uuid::new_v4())
277 }
278
279 pub fn from_uuid(id: Uuid) -> Self {
281 Self(id)
282 }
283
284 pub fn as_uuid(&self) -> Uuid {
286 self.0
287 }
288}
289
290impl fmt::Display for ExchangeId {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 write!(f, "{}", self.0)
293 }
294}
295
296#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
301pub struct SessionId(String);
302
303impl SessionId {
304 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
306 Ok(Self(validated_string(value)?))
307 }
308
309 pub fn generate() -> Self {
311 Self(Uuid::new_v4().to_string())
312 }
313
314 pub fn as_str(&self) -> &str {
316 &self.0
317 }
318
319 pub fn as_external(&self) -> ExternalSessionId {
321 ExternalSessionId(self.0.clone())
322 }
323
324 pub fn into_external(self) -> ExternalSessionId {
326 ExternalSessionId(self.0)
327 }
328
329 pub fn from_external(id: &ExternalSessionId) -> Self {
331 Self(id.0.clone())
332 }
333}
334
335impl fmt::Display for SessionId {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 f.write_str("<session>")
338 }
339}
340
341#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
343pub struct ChannelId(String);
344
345impl ChannelId {
346 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
348 Ok(Self(validated_string(value)?))
349 }
350
351 pub fn as_str(&self) -> &str {
353 &self.0
354 }
355}
356
357impl fmt::Display for ChannelId {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 f.write_str(&self.0)
360 }
361}
362
363#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
367pub struct SessionKey {
368 pub channel_id: ChannelId,
370 pub session_id: SessionId,
372}
373
374impl SessionKey {
375 pub fn new(channel_id: ChannelId, session_id: SessionId) -> Self {
377 Self {
378 channel_id,
379 session_id,
380 }
381 }
382}
383
384#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
386pub struct ToolId(String);
387
388impl ToolId {
389 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
391 Ok(Self(validated_string(value)?))
392 }
393
394 pub fn as_str(&self) -> &str {
396 &self.0
397 }
398}
399
400impl fmt::Display for ToolId {
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 f.write_str(&self.0)
403 }
404}
405
406#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
408pub struct ToolName(String);
409
410impl ToolName {
411 pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
413 Ok(Self(validated_string(value)?))
414 }
415
416 pub fn as_str(&self) -> &str {
418 &self.0
419 }
420}
421
422impl fmt::Display for ToolName {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 f.write_str(&self.0)
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn rejects_empty_and_control() {
434 assert_eq!(SessionId::try_new(""), Err(IdentityError::Empty));
435 assert_eq!(
436 ChannelId::try_new("a\nb"),
437 Err(IdentityError::ControlCharacter)
438 );
439 assert!(ToolId::try_new("x".repeat(MAX_IDENTITY_BYTES + 1)).is_err());
440 }
441
442 #[test]
443 fn session_key_isolates_channels() {
444 let a = SessionKey::new(
445 ChannelId::try_new("ch-a").unwrap(),
446 SessionId::try_new("same-sess").unwrap(),
447 );
448 let b = SessionKey::new(
449 ChannelId::try_new("ch-b").unwrap(),
450 SessionId::try_new("same-sess").unwrap(),
451 );
452 assert_ne!(a, b);
453 assert_eq!(a.session_id.as_str(), b.session_id.as_str());
454 }
455
456 #[test]
457 fn session_external_round_trip_bytes() {
458 let ext = ExternalSessionId::try_new("provider-abc").unwrap();
459 let sid = SessionId::from_external(&ext);
460 assert_eq!(sid.as_str(), ext.as_str());
461 assert_eq!(sid.into_external().as_str(), "provider-abc");
462 }
463
464 #[test]
465 fn transaction_id_serializes() {
466 let id = TransactionId::generate();
467 let json = serde_json::to_string(&id).unwrap();
468 let back: TransactionId = serde_json::from_str(&json).unwrap();
469 assert_eq!(id, back);
470 }
471}