use crate::session::{SessionId, SessionState};
use crate::session_store::cookie_generator::SessionCookieGenerator;
use crate::{DefaultSessionCookieGenerator, Error, Session, SessionExpiry};
use async_trait::async_trait;
use chrono::Utc;
use chrono::{DateTime, Duration};
use std::fmt::Debug;
use std::marker::PhantomData;
pub(crate) mod cookie_generator;
#[derive(Debug)]
pub struct SessionStore<
SessionData,
SessionStoreConnection,
CookieGenerator = DefaultSessionCookieGenerator,
> {
cookie_generator: CookieGenerator,
session_renewal_strategy: SessionRenewalStrategy,
data: PhantomData<SessionData>,
connection: PhantomData<SessionStoreConnection>,
}
#[derive(Clone, Copy, Debug)]
pub enum SessionRenewalStrategy {
Ignore,
AutomaticRenewal {
time_to_live: Duration,
maximum_remaining_time_to_live_for_renewal: Duration,
},
}
impl<SessionData, SessionStoreConnection>
SessionStore<SessionData, SessionStoreConnection, DefaultSessionCookieGenerator>
{
pub fn new(expiry_strategy: SessionRenewalStrategy) -> Self {
Self {
cookie_generator: Default::default(),
session_renewal_strategy: expiry_strategy,
data: Default::default(),
connection: Default::default(),
}
}
}
impl<SessionData, SessionStoreConnection, CookieGenerator>
SessionStore<SessionData, SessionStoreConnection, CookieGenerator>
{
pub fn new_with_cookie_generator(
cookie_generator: CookieGenerator,
session_renewal_strategy: SessionRenewalStrategy,
) -> Self {
Self {
cookie_generator,
session_renewal_strategy,
data: Default::default(),
connection: Default::default(),
}
}
pub fn session_renewal_strategy(&self) -> &SessionRenewalStrategy {
&self.session_renewal_strategy
}
pub fn session_renewal_strategy_mut(&mut self) -> &mut SessionRenewalStrategy {
&mut self.session_renewal_strategy
}
}
impl<
SessionData: Debug,
SessionStoreConnection: SessionStoreConnector<SessionData>,
CookieGenerator: SessionCookieGenerator,
> SessionStore<SessionData, SessionStoreConnection, CookieGenerator>
{
pub async fn store_session(
&self,
mut session: Session<SessionData>,
connection: &mut SessionStoreConnection,
) -> Result<SessionCookieCommand, Error<SessionStoreConnection::Error>> {
if matches!(
&session.state,
SessionState::NewChanged { .. }
| SessionState::Changed { .. }
| SessionState::Deleted { .. }
) {
if matches!(&session.state, SessionState::NewChanged { .. }) {
self.session_renewal_strategy
.apply_to_session(&mut session, Utc::now());
}
if let Some(maximum_retries_on_collision) = connection.maximum_retries_on_id_collision()
{
for _ in 0..maximum_retries_on_collision {
match self.try_store_session(&session, connection).await? {
WriteSessionResult::Ok(command) => return Ok(command),
WriteSessionResult::SessionIdExists => { }
}
}
Err(Error::MaximumSessionIdGenerationTriesReached {
maximum: maximum_retries_on_collision,
})
} else {
loop {
match self.try_store_session(&session, connection).await? {
WriteSessionResult::Ok(command) => return Ok(command),
WriteSessionResult::SessionIdExists => { }
}
}
}
} else {
Ok(SessionCookieCommand::DoNothing)
}
}
async fn try_store_session(
&self,
session: &Session<SessionData>,
connection: &mut SessionStoreConnection,
) -> Result<WriteSessionResult<SessionCookieCommand>, Error<SessionStoreConnection::Error>>
{
match &session.state {
SessionState::NewChanged { expiry, data } => {
let cookie_value = self.cookie_generator.generate_cookie();
let id = SessionId::from_cookie_value(&cookie_value);
Ok(connection
.create_session(&id, expiry, data)
.await?
.map(|()| SessionCookieCommand::Set {
cookie_value,
expiry: *expiry,
}))
}
SessionState::Changed {
current_id: previous_id,
expiry,
data,
} => {
let cookie_value = self.cookie_generator.generate_cookie();
let current_id = SessionId::from_cookie_value(&cookie_value);
Ok(connection
.update_session(¤t_id, previous_id, expiry, data)
.await?
.map(|()| SessionCookieCommand::Set {
cookie_value,
expiry: *expiry,
}))
}
SessionState::Deleted { current_id } => {
connection.delete_session(current_id).await?;
Ok(WriteSessionResult::Ok(SessionCookieCommand::Delete))
}
SessionState::NewUnchanged { .. }
| SessionState::Unchanged { .. }
| SessionState::NewDeleted => unreachable!(),
SessionState::Invalid => unreachable!("Invalid state is used internally only"),
}
}
pub async fn clear_store(
&self,
connection: &mut SessionStoreConnection,
) -> Result<(), Error<SessionStoreConnection::Error>> {
connection.clear().await
}
pub async fn load_session(
&self,
cookie_value: impl AsRef<str>,
connection: &mut SessionStoreConnection,
) -> Result<Option<Session<SessionData>>, Error<SessionStoreConnection::Error>> {
if cookie_value.as_ref().as_bytes().len() != CookieGenerator::COOKIE_LENGTH {
return Err(Error::WrongCookieLength {
expected: CookieGenerator::COOKIE_LENGTH,
actual: cookie_value.as_ref().as_bytes().len(),
});
}
let session_id = SessionId::from_cookie_value(cookie_value.as_ref());
if let Some(mut session) = connection.read_session(session_id).await? {
let now = Utc::now();
if session.is_expired(now) {
return Ok(None);
}
self.session_renewal_strategy
.apply_to_session(&mut session, now);
Ok(Some(session))
} else {
Ok(None)
}
}
}
impl<SessionData, SessionStoreConnection, CookieGenerator: Clone> Clone
for SessionStore<SessionData, SessionStoreConnection, CookieGenerator>
{
fn clone(&self) -> Self {
Self {
cookie_generator: self.cookie_generator.clone(),
session_renewal_strategy: self.session_renewal_strategy,
data: self.data,
connection: self.connection,
}
}
}
#[async_trait]
pub trait SessionStoreConnector<SessionData> {
type Error: Debug;
fn maximum_retries_on_id_collision(&self) -> Option<u32>;
async fn create_session(
&mut self,
current_id: &SessionId,
expiry: &SessionExpiry,
data: &SessionData,
) -> Result<WriteSessionResult, Error<Self::Error>>;
async fn read_session(
&mut self,
id: SessionId,
) -> Result<Option<Session<SessionData>>, Error<Self::Error>>;
async fn update_session(
&mut self,
current_id: &SessionId,
previous_id: &SessionId,
expiry: &SessionExpiry,
data: &SessionData,
) -> Result<WriteSessionResult, Error<Self::Error>>;
async fn delete_session(&mut self, id: &SessionId) -> Result<(), Error<Self::Error>>;
async fn clear(&mut self) -> Result<(), Error<Self::Error>>;
}
#[derive(Debug)]
#[must_use]
pub enum WriteSessionResult<OkData = ()> {
Ok(OkData),
SessionIdExists,
}
impl<OkData> WriteSessionResult<OkData> {
fn map<OtherOkData>(
self,
f: impl FnOnce(OkData) -> OtherOkData,
) -> WriteSessionResult<OtherOkData> {
match self {
Self::Ok(data) => WriteSessionResult::Ok(f(data)),
Self::SessionIdExists => WriteSessionResult::SessionIdExists,
}
}
}
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub enum SessionCookieCommand {
Set {
cookie_value: String,
expiry: SessionExpiry,
},
Delete,
DoNothing,
}
impl SessionRenewalStrategy {
fn apply_to_session<SessionData: Debug>(
&self,
session: &mut Session<SessionData>,
now: DateTime<Utc>,
) {
match self {
SessionRenewalStrategy::Ignore => { }
SessionRenewalStrategy::AutomaticRenewal {
time_to_live,
maximum_remaining_time_to_live_for_renewal,
} => {
let new_expiry = now + *time_to_live;
match *session.expiry() {
SessionExpiry::DateTime(old_expiry) => {
if old_expiry - now <= *maximum_remaining_time_to_live_for_renewal {
session.set_expiry(new_expiry);
}
}
SessionExpiry::Never => session.set_expiry(new_expiry),
}
}
}
}
}