use std::{
marker::{Send, Sync},
sync::{Arc, Mutex},
};
use bon::Builder;
use rocket::{fairing::Fairing, Build, Orbit, Request, Response, Rocket};
use crate::{
guard::LocalCachedSession,
storage::{memory::MemoryStorage, SessionStorage},
RocketFlexSessionOptions,
};
#[derive(Builder)]
pub struct RocketFlexSession<T: Send + Sync + Clone + 'static> {
#[builder(default)]
pub(crate) options: RocketFlexSessionOptions,
#[builder(default = Arc::new(MemoryStorage::default()), with = |storage: impl SessionStorage<T> + 'static| Arc::new(storage))]
pub(crate) storage: Arc<dyn SessionStorage<T>>,
}
impl<T> Default for RocketFlexSession<T>
where
T: Send + Sync + Clone + 'static,
{
fn default() -> Self {
Self {
options: Default::default(),
storage: Arc::new(MemoryStorage::default()),
}
}
}
use rocket_flex_session_builder::{IsUnset, SetOptions, State};
impl<T, S> RocketFlexSessionBuilder<T, S>
where
T: Send + Sync + Clone + 'static,
S: State,
{
pub fn with_options<OptionsFn>(
self,
options_fn: OptionsFn,
) -> RocketFlexSessionBuilder<T, SetOptions<S>>
where
S::Options: IsUnset,
OptionsFn: FnOnce(&mut RocketFlexSessionOptions),
{
let mut options = RocketFlexSessionOptions::default();
options_fn(&mut options);
self.options(options)
}
}
#[rocket::async_trait]
impl<T> Fairing for RocketFlexSession<T>
where
T: Send + Sync + Clone + 'static,
{
fn info(&self) -> rocket::fairing::Info {
use rocket::fairing::Kind;
rocket::fairing::Info {
name: "Rocket Flex Session",
kind: Kind::Ignite | Kind::Response | Kind::Shutdown | Kind::Singleton,
}
}
async fn on_ignite(&self, rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>> {
rocket::debug!("Setting up session resources...");
if let Err(e) = self.storage.setup().await {
rocket::warn!("Error during session storage setup: {}", e);
}
Ok(rocket.manage::<RocketFlexSession<T>>(RocketFlexSession {
options: self.options.clone(),
storage: self.storage.clone(),
}))
}
async fn on_response<'r>(&self, req: &'r Request<'_>, _res: &mut Response<'r>) {
let (session_inner, _): &LocalCachedSession<T> =
req.local_cache(|| (Mutex::default(), None));
let (updated, deleted) = session_inner.lock().unwrap().take_for_storage();
if let Some((id, data)) = deleted {
rocket::debug!("Found deleted session. Deleting session '{id}'...");
if let Err(e) = self.storage.delete(&id, data).await {
rocket::warn!("Error while deleting session '{id}': {e}");
} else {
rocket::debug!("Deleted session '{id}' successfully");
}
}
if let Some((id, data, ttl)) = updated {
rocket::debug!("Found updated session. Saving session '{id}'...");
if let Err(e) = self.storage.save(&id, data, ttl).await {
rocket::error!("Error while saving session '{id}': {e}");
} else {
rocket::debug!("Saved session '{id}' successfully");
}
}
}
async fn on_shutdown(&self, _rocket: &Rocket<Orbit>) {
rocket::debug!("Shutting down session resources...");
if let Err(e) = self.storage.shutdown().await {
rocket::warn!("Error during session storage shutdown: {e}");
}
}
}