tower_sessions_file_based_store/
lib.rs

1//! # tower-sessions-file-store
2//! 
3//! `tower-sessions-file-store` is a simple and minimalistic file store backing provider for
4//! `tower-sessions`.  Usage is extremely simple;
5//! 
6//! ## Example:
7//! ```
8//!     let session_store = tower_sessions_file_store::FileStore::new("/path/to/sessions/directory", "prefix-", ".json");
9//!     let session_layer = tower_sessions::SessionManagerLayer::new(session_store)
10//!         .with_secure(false)
11//!         .with_expiry(tower_sessions::Expiry::OnInactivity(Duration::seconds(15)))
12//!         ;
13//!     Router::new()
14//!         .route("/sess_test", get(handle_sess_test));
15//!         .layer(session_layer)
16//!         ;
17//!     
18//!     /* ... Elsewhere ... */
19//!     async fn handle_sess_test(sess: tower_sessions::Session) -> impl axum::response::IntoResponse {
20//!         let counter: u32 = sess.get("count").await.unwrap().unwrap_or(0u32);
21//!         let _ = sess.insert("count", counter + 1).await;
22//!         format!("Count is {counter}.")
23//!     }
24//!     
25//! ```
26
27use axum::async_trait;
28use std::fs;
29use tower_sessions::{
30    self,
31    session::{Id, Record},
32    session_store::{
33        self,
34        Error::Decode,
35    },
36};
37
38/// Creates a FileStore struct and stores its configuration.  Specifying the `dir`, `prefix`, and
39/// `extension` fields will define how session.
40/// 
41/// For example, if you were to use:
42/// ```
43///     FileStore::new("/path/to/sesssions/directory", "prefix-", ".json")
44/// ```
45/// to instantiate a new `FileStore` struct, then you would end up with files such as:
46/// 
47/// ```bash
48///     /path/to/sesssions/directory/prefix-CI4afkzk6tVMRb50lMyZAA.json
49///     /path/to/sesssions/directory/prefix-Hs8Jb0_zAGrc_rmUYGwlvw.json
50///     /path/to/sesssions/directory/prefix-swJdTjvk1os8zAhhc6AVMQ.json
51/// ```
52/// 
53/// 
54#[derive(Clone, Debug, Default)]
55pub struct FileStore {
56    /// Directory to use for session storage.  Omit any trailing slashes or path separators.
57    pub dir: &'static str,
58    /// Optional prefix for session files.  If not empty, all files will begin with this prefix
59    pub prefix: &'static str,
60    pub extension: &'static str,
61}
62
63impl FileStore {
64    /// Creates a new `FileStore` struct with the specified `dir`, `prefix`, and `extensions` fields.
65    pub fn new(dir: &'static str, prefix: &'static str, extension: &'static str) -> Self {
66        Self {
67            dir,
68            prefix,
69            extension,
70        }
71    }
72    /// Creates a new `FileStore` struct with the specified `dir` field and blank `prefix` and
73    /// `extension` fields.
74    pub fn in_dir(d: &'static str) -> Self {
75        Self {
76            dir: d,
77            prefix: "",
78            extension: "",
79        }
80    }
81    /// Returns the full path a session with the given `session_id` should be found or created at.
82    pub fn path(&self, session_id: &Id) -> String {
83        String::new()
84            + self.dir
85            + std::path::MAIN_SEPARATOR.to_string().as_str()
86            + self.prefix
87            + session_id.to_string().as_str()
88            + self.extension
89    }
90    /// Internal function for saving a session.  Note that depending on the host file system, this
91    /// could be susceptible to clobbering / race conditions.  If you expect multiple concurrent
92    /// saves to the same session ID, this may not be the ideal tool for you to use.  Its chief
93    /// goals are simplicity and lack of reliance upon external tooling and if you seek stronger
94    /// ACID guarantees you should consider another storage system.
95    fn save(&self, record: &Record) -> session_store::Result<()> {
96        let serialized = serde_json::to_string(&record).map_err(|e| Decode(e.to_string()))?;
97        fs::write(self.path(&record.id), serialized).map_err(|e| Decode(e.to_string()))
98    }
99}
100
101#[async_trait]
102/// Implementation of tower_sessions::SessionStore
103/// This powers the session storage and deletion system.
104/// Note that the self.save() and self.path() calls refer to `impl FileStore`
105impl session_store::SessionStore for FileStore {
106    async fn create(&self, record: &mut Record) -> session_store::Result<()> {
107        self.save(record)
108    }
109    async fn save(&self, record: &Record) -> session_store::Result<()> {
110        self.save(record)
111    }
112    async fn load(&self, session_id: &Id) -> session_store::Result<Option<Record>> {
113        let data: String = fs::read_to_string(self.path(session_id)).map_err(|e| Decode(e.to_string()))?;
114        let record: Record = serde_json::from_str(data.as_str()).map_err(|e| Decode(e.to_string()))?;
115        Ok(Some(record))
116    }
117    async fn delete(&self, session_id: &Id) -> session_store::Result<()> {
118        fs::remove_file(self.path(session_id)).map_err(|e| Decode(e.to_string()))
119    }
120}
121
122