pub use chat_engine_sdk::models::{LifecycleState, Session, SessionType};
use serde_json::{Map, Value};
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
use crate::domain::error::{ChatEngineError, Result};
use crate::domain::memory_strategy::MemoryStrategy;
use crate::domain::retention::RetentionPolicy;
pub const METADATA_KEY_MEMORY_STRATEGY: &str = "memory_strategy";
pub const METADATA_KEY_RETENTION_POLICY: &str = "retention_policy";
pub const METADATA_KEY_SHARE_EXPIRES_AT: &str = "share_expires_at";
pub const RESERVED_METADATA_KEYS: &[&str] = &[
METADATA_KEY_MEMORY_STRATEGY,
METADATA_KEY_RETENTION_POLICY,
METADATA_KEY_SHARE_EXPIRES_AT,
];
#[must_use]
pub fn get_memory_strategy(s: &Session) -> Option<MemoryStrategy> {
metadata_value(s, METADATA_KEY_MEMORY_STRATEGY)
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn set_memory_strategy(s: &mut Session, v: MemoryStrategy) {
if let Ok(encoded) = serde_json::to_value(&v) {
set_metadata_value(s, METADATA_KEY_MEMORY_STRATEGY, encoded);
}
}
#[must_use]
pub fn get_retention_policy(s: &Session) -> Option<RetentionPolicy> {
metadata_value(s, METADATA_KEY_RETENTION_POLICY)
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn set_retention_policy(s: &mut Session, v: RetentionPolicy) {
if let Ok(encoded) = serde_json::to_value(&v) {
set_metadata_value(s, METADATA_KEY_RETENTION_POLICY, encoded);
}
}
#[must_use]
pub fn get_share_expires_at(s: &Session) -> Option<OffsetDateTime> {
metadata_value(s, METADATA_KEY_SHARE_EXPIRES_AT)
.and_then(|v| v.as_str())
.and_then(|raw| OffsetDateTime::parse(raw, &Rfc3339).ok())
}
pub fn set_share_expires_at(s: &mut Session, v: Option<OffsetDateTime>) {
match v {
Some(ts) => {
if let Ok(encoded) = ts.format(&Rfc3339) {
set_metadata_value(s, METADATA_KEY_SHARE_EXPIRES_AT, Value::String(encoded));
}
}
None => {
remove_metadata_value(s, METADATA_KEY_SHARE_EXPIRES_AT);
}
}
}
#[must_use]
pub fn public_metadata(s: &Session) -> Option<Value> {
let Some(Value::Object(map)) = s.metadata.as_ref() else {
return s.metadata.clone();
};
let filtered: Map<String, Value> = map
.iter()
.filter(|(k, _)| !RESERVED_METADATA_KEYS.contains(&k.as_str()))
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
if filtered.is_empty() {
None
} else {
Some(Value::Object(filtered))
}
}
pub fn ensure_can_transition(from: LifecycleState, to: LifecycleState) -> Result<()> {
if from.can_transition_to(&to) {
Ok(())
} else {
Err(ChatEngineError::invalid_transition(from, to))
}
}
fn metadata_value<'a>(s: &'a Session, key: &str) -> Option<&'a Value> {
s.metadata
.as_ref()
.and_then(|v| v.as_object())
.and_then(|map| map.get(key))
}
fn set_metadata_value(s: &mut Session, key: &str, value: Value) {
match s.metadata.as_mut().and_then(|v| v.as_object_mut()) {
Some(map) => {
map.insert(key.to_string(), value);
}
None => {
let mut map = Map::new();
map.insert(key.to_string(), value);
s.metadata = Some(Value::Object(map));
}
}
}
fn remove_metadata_value(s: &mut Session, key: &str) {
if let Some(map) = s.metadata.as_mut().and_then(|v| v.as_object_mut()) {
map.remove(key);
if map.is_empty() {
s.metadata = None;
}
}
}
#[cfg(test)]
#[path = "session_tests.rs"]
mod session_tests;