use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
pub mod terminal;
pub use terminal::SaTerminalInfo;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SaSession {
pub id: String,
pub create_time: DateTime<Utc>,
#[serde(default)]
pub terminal_list: Vec<SaTerminalInfo>,
#[serde(default)]
pub history_terminal_count: i32,
#[serde(flatten)]
pub data: HashMap<String, serde_json::Value>,
}
impl SaSession {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
create_time: Utc::now(),
terminal_list: Vec::new(),
history_terminal_count: 0,
data: HashMap::new(),
}
}
pub fn set<T: Serialize>(&mut self, key: impl Into<String>, value: T) -> Result<(), serde_json::Error> {
let json_value = serde_json::to_value(value)?;
self.data.insert(key.into(), json_value);
Ok(())
}
pub fn get<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
self.data.get(key)
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn remove(&mut self, key: &str) -> Option<serde_json::Value> {
self.data.remove(key)
}
pub fn clear(&mut self) {
self.data.clear();
}
pub fn has(&self, key: &str) -> bool {
self.data.contains_key(key)
}
pub fn add_terminal(&mut self, mut terminal: SaTerminalInfo) {
self.history_terminal_count += 1;
terminal.index = self.history_terminal_count;
self.terminal_list.push(terminal);
}
pub fn remove_terminal(&mut self, token_value: &str) -> Option<SaTerminalInfo> {
if let Some(pos) = self.terminal_list.iter().position(|t| t.token_value == token_value) {
Some(self.terminal_list.remove(pos))
} else {
None
}
}
pub fn get_terminal(&self, token_value: &str) -> Option<&SaTerminalInfo> {
self.terminal_list.iter().find(|t| t.token_value == token_value)
}
pub fn terminal_list_copy(&self) -> Vec<SaTerminalInfo> {
self.terminal_list.clone()
}
pub fn get_terminal_list_by_device_type(&self, device_type: Option<&str>) -> Vec<SaTerminalInfo> {
match device_type {
None => self.terminal_list.clone(),
Some(dt) => self
.terminal_list
.iter()
.filter(|t| t.device_type == dt)
.cloned()
.collect(),
}
}
pub fn get_token_value_list_by_device_type(&self, device_type: Option<&str>) -> Vec<String> {
self.get_terminal_list_by_device_type(device_type)
.into_iter()
.map(|t| t.token_value)
.collect()
}
pub fn terminal_count(&self) -> usize {
self.terminal_list.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_terminal_index_increments() {
let mut session = SaSession::new("u1");
session.add_terminal(SaTerminalInfo::new("t1", "PC"));
session.add_terminal(SaTerminalInfo::new("t2", "APP"));
session.add_terminal(SaTerminalInfo::new("t3", "WEB"));
assert_eq!(session.terminal_count(), 3);
assert_eq!(session.history_terminal_count, 3);
assert_eq!(session.terminal_list[0].index, 1);
assert_eq!(session.terminal_list[1].index, 2);
assert_eq!(session.terminal_list[2].index, 3);
session.remove_terminal("t2");
session.add_terminal(SaTerminalInfo::new("t4", "PC"));
assert_eq!(session.terminal_list.last().unwrap().index, 4);
}
#[test]
fn test_filter_by_device_type() {
let mut session = SaSession::new("u1");
session.add_terminal(SaTerminalInfo::new("t1", "PC"));
session.add_terminal(SaTerminalInfo::new("t2", "PC"));
session.add_terminal(SaTerminalInfo::new("t3", "APP"));
assert_eq!(session.get_terminal_list_by_device_type(Some("PC")).len(), 2);
assert_eq!(session.get_terminal_list_by_device_type(None).len(), 3);
assert_eq!(
session.get_token_value_list_by_device_type(Some("APP")),
vec!["t3".to_string()]
);
}
#[test]
fn test_deserialize_legacy_session_without_terminals() {
let json = r#"{"id":"u1","create_time":"2024-01-01T00:00:00Z","foo":"bar"}"#;
let session: SaSession = serde_json::from_str(json).unwrap();
assert!(session.terminal_list.is_empty());
assert_eq!(session.history_terminal_count, 0);
}
}