use rand::distr::{Alphanumeric, SampleString};
use crate::SessionIdentifier;
#[derive(Debug)]
pub(crate) struct SessionInner<T> {
current: Option<ActiveSession<T>>,
deleted: Option<ActiveSession<T>>,
}
impl<T> Default for SessionInner<T> {
fn default() -> Self {
Self::new_empty()
}
}
#[derive(Debug)]
struct ActiveSession<T> {
id: String,
data: T,
ttl: u32,
status: ActiveSessionStatus,
}
#[derive(Debug, PartialEq, Eq)]
enum ActiveSessionStatus {
New,
Existing,
Updated,
}
impl<T> ActiveSession<T> {
fn new(new_data: T, ttl: u32) -> Self {
Self {
id: Alphanumeric.sample_string(&mut rand::rng(), 20),
data: new_data,
ttl,
status: ActiveSessionStatus::New,
}
}
fn existing(id: &str, data: T, ttl: u32) -> ActiveSession<T> {
Self {
id: id.to_owned(),
data,
ttl,
status: ActiveSessionStatus::Existing,
}
}
}
impl<T> SessionInner<T> {
pub(crate) fn new_empty() -> Self {
Self {
current: None,
deleted: None,
}
}
pub(crate) fn new_existing(id: &str, data: T, ttl: u32) -> Self {
Self {
current: Some(ActiveSession::existing(id, data, ttl)),
deleted: None,
}
}
pub(crate) fn get_id(&self) -> Option<&str> {
self.current.as_ref().map(|s| s.id.as_str())
}
pub(crate) fn get_current_data(&self) -> Option<&T> {
self.current.as_ref().map(|s| &s.data)
}
pub(crate) fn get_current_ttl(&self) -> Option<u32> {
self.current.as_ref().map(|s| s.ttl)
}
pub(crate) fn is_new(&self) -> bool {
self.current
.as_ref()
.map_or(false, |s| s.status == ActiveSessionStatus::New)
}
pub(crate) fn set_data(&mut self, new_data: T, default_ttl: u32) {
match &mut self.current {
Some(current) => {
current.data = new_data;
self.mark_updated();
}
None => self.current = Some(ActiveSession::new(new_data, default_ttl)),
}
}
pub(crate) fn set_ttl(&mut self, new_ttl: u32) {
if let Some(current) = &mut self.current {
current.ttl = new_ttl;
self.mark_updated();
}
}
pub(crate) fn tap_data_mut<UpdateFn, R>(
&mut self,
callback: UpdateFn,
default_ttl: u32,
) -> (R, bool)
where
UpdateFn: FnOnce(&mut Option<T>) -> R,
{
match self.current.take() {
Some(current) => {
let mut updated_data = Some(current.data);
let response = callback(&mut updated_data);
if let Some(data) = updated_data {
self.current = Some(ActiveSession { data, ..current });
self.mark_updated();
(response, false)
} else {
self.delete();
(response, true)
}
}
None => {
let mut new_data: Option<T> = None;
let response = callback(&mut new_data);
if let Some(data) = new_data {
self.current = Some(ActiveSession::new(data, default_ttl));
(response, false)
} else {
self.delete();
(response, true)
}
}
}
}
pub(crate) fn mark_updated(&mut self) {
if let Some(current) = self.current.as_mut() {
if current.status == ActiveSessionStatus::Existing {
current.status = ActiveSessionStatus::Updated;
}
}
}
pub(crate) fn delete(&mut self) {
if let Some(current) = self.current.take() {
self.deleted.get_or_insert(current);
}
}
pub(crate) fn get_deleted_id(&self) -> Option<&str> {
self.deleted.as_ref().map(|s| s.id.as_str())
}
pub(crate) fn take_for_storage(&mut self) -> (Option<(String, T, u32)>, Option<(String, T)>) {
let updated_session = self
.current
.take()
.filter(|c| should_save_session(&c.status))
.map(|c| (c.id, c.data, c.ttl));
(updated_session, self.deleted.take().map(|s| (s.id, s.data)))
}
}
fn should_save_session(status: &ActiveSessionStatus) -> bool {
*status == ActiveSessionStatus::New || *status == ActiveSessionStatus::Updated
}
impl<T> SessionInner<T>
where
T: SessionIdentifier,
{
pub(crate) fn get_current_identifier(&self) -> Option<T::Id> {
self.get_current_data().and_then(|data| data.identifier())
}
}