systemprompt_identifiers/
client_session.rs1use crate::error::IdValidationError;
21
22const SESSION_SEGMENT: &str = "_session_";
23
24const MAX_LEN: usize = 128;
25
26fn validate(value: &str) -> Result<(), IdValidationError> {
27 if value.is_empty() || value.len() > MAX_LEN {
28 return Err(IdValidationError::invalid(
29 "ClientSessionId",
30 "must be 1 to 128 characters",
31 ));
32 }
33 if !value.bytes().all(|b| b.is_ascii_graphic()) {
34 return Err(IdValidationError::invalid(
35 "ClientSessionId",
36 "must be printable ASCII with no whitespace",
37 ));
38 }
39 Ok(())
40}
41
42crate::define_id!(ClientSessionId, validated, schema, validate);
43
44#[derive(serde::Deserialize)]
45struct MetadataUserId {
46 #[serde(rename = "session_id")]
47 raw_session: String,
48}
49
50impl ClientSessionId {
51 pub fn from_metadata_user_id(value: &str) -> Result<Option<Self>, IdValidationError> {
52 let value = value.trim();
53 let session = if value.starts_with('{') {
54 let metadata: MetadataUserId = serde_json::from_str(value)
55 .map_err(|e| IdValidationError::json("ClientSessionId", e))?;
56 Some(metadata.raw_session)
57 } else {
58 value
59 .rsplit_once(SESSION_SEGMENT)
60 .map(|(_, suffix)| suffix.to_owned())
61 };
62 session
63 .map(|value| {
64 let parsed = uuid::Uuid::parse_str(value.trim())
65 .map_err(|e| IdValidationError::uuid("ClientSessionId", e))?;
66 Self::try_new(parsed.hyphenated().to_string())
67 })
68 .transpose()
69 }
70}