greentic_types/
session.rs1use alloc::borrow::ToOwned;
4use alloc::string::String;
5
6#[cfg(feature = "schemars")]
7use schemars::JsonSchema;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use crate::{FlowId, TenantCtx};
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[cfg_attr(feature = "schemars", derive(JsonSchema))]
17#[cfg_attr(feature = "serde", serde(transparent))]
18pub struct SessionKey(pub String);
19
20impl SessionKey {
21 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25
26 pub fn new(value: impl Into<String>) -> Self {
28 Self(value.into())
29 }
30
31 #[cfg(feature = "uuid")]
33 pub fn generate() -> Self {
34 Self(uuid::Uuid::new_v4().to_string())
35 }
36}
37
38impl From<String> for SessionKey {
39 fn from(value: String) -> Self {
40 Self(value)
41 }
42}
43
44impl From<&str> for SessionKey {
45 fn from(value: &str) -> Self {
46 Self(value.to_owned())
47 }
48}
49
50impl core::fmt::Display for SessionKey {
51 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52 f.write_str(self.as_str())
53 }
54}
55
56#[cfg(feature = "uuid")]
57impl From<uuid::Uuid> for SessionKey {
58 fn from(value: uuid::Uuid) -> Self {
59 Self(value.to_string())
60 }
61}
62
63const DEFAULT_CANONICAL_ANCHOR: &str = "conversation";
64const DEFAULT_CANONICAL_USER: &str = "user";
65
66pub fn canonical_session_key(
72 tenant: impl AsRef<str>,
73 provider: impl AsRef<str>,
74 anchor: Option<&str>,
75 user: Option<&str>,
76) -> SessionKey {
77 SessionKey::new(format!(
78 "{}:{}:{}:{}",
79 tenant.as_ref(),
80 provider.as_ref(),
81 anchor.unwrap_or(DEFAULT_CANONICAL_ANCHOR),
82 user.unwrap_or(DEFAULT_CANONICAL_USER)
83 ))
84}
85
86#[derive(Clone, Debug, PartialEq, Eq, Hash)]
88#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
89#[cfg_attr(feature = "schemars", derive(JsonSchema))]
90pub struct SessionCursor {
91 pub node_pointer: String,
93 #[cfg_attr(
95 feature = "serde",
96 serde(default, skip_serializing_if = "Option::is_none")
97 )]
98 pub wait_reason: Option<String>,
99 #[cfg_attr(
101 feature = "serde",
102 serde(default, skip_serializing_if = "Option::is_none")
103 )]
104 pub outbox_marker: Option<String>,
105}
106
107impl SessionCursor {
108 pub fn new(node_pointer: impl Into<String>) -> Self {
110 Self {
111 node_pointer: node_pointer.into(),
112 wait_reason: None,
113 outbox_marker: None,
114 }
115 }
116
117 pub fn with_wait_reason(mut self, reason: impl Into<String>) -> Self {
119 self.wait_reason = Some(reason.into());
120 self
121 }
122
123 pub fn with_outbox_marker(mut self, marker: impl Into<String>) -> Self {
125 self.outbox_marker = Some(marker.into());
126 self
127 }
128}
129
130#[derive(Clone, Debug, PartialEq, Eq)]
132#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
133#[cfg_attr(feature = "schemars", derive(JsonSchema))]
134pub struct SessionData {
135 pub tenant_ctx: TenantCtx,
137 pub flow_id: FlowId,
139 pub cursor: SessionCursor,
141 pub context_json: String,
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn canonical_session_key_includes_components() {
151 let key = canonical_session_key("tenant", "webhook", Some("room-1"), Some("user-5"));
152 assert_eq!(key.as_str(), "tenant:webhook:room-1:user-5");
153 }
154
155 #[test]
156 fn canonical_session_key_defaults_anchor_and_user() {
157 let key = canonical_session_key("tenant", "webhook", None, None);
158 assert_eq!(key.as_str(), "tenant:webhook:conversation:user");
159 }
160}