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