diff --git a/.gitignore b/.gitignore index b75a144..30b03f4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ target/ tmp/ Cargo.lock .DS_Store +.vscode/launch.json +.cargo/config.toml diff --git a/Cargo.toml b/Cargo.toml index b0e31d1..e72aa01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,33 +1,39 @@ [package] name = "async-mongodb-session" -version = "2.0.0" +version = "3.0.0" license = "MIT OR Apache-2.0" repository = "https://github.com/yoshuawuyts/async-mongodb-session" documentation = "https://docs.rs/async-mongodb-session" description = "An async-session implementation for MongoDB" readme = "README.md" -edition = "2018" +edition = "2021" keywords = ["tide", "web", "async", "session", "mongodb"] categories = [ "network-programming", "asynchronous", - "web-programming::http-server" + "web-programming::http-server", ] authors = [ - "Yoshua Wuyts ", - "Irina Shestak ", - "Anton Whalley ", - "Javier Viola " + "Yoshua Wuyts ", + "Irina Shestak ", + "Anton Whalley ", + "Javier Viola ", + "Aaron Erhardt ", ] [features] +default = ["async-std-runtime"] +async-std-runtime = ["mongodb/async-std-runtime"] +tokio-runtime = ["mongodb/tokio-runtime"] [dependencies] -mongodb = { version = "1.1.1", default-features = false, features = ["async-std-runtime"] } -async-session = "2.0.0" +async-session = "3" +mongodb = { package = "mongodb", version = "2.3", default-features = false, features = [ + "bson-chrono-0_4", +] } [dev-dependencies] -async-std = { version = "1.8.0", features = ["attributes"] } -rand = {version = "0.7.3"} -lazy_static = "1" -tide = "0.15" +async-std = { version = "1.12", features = ["attributes"] } +lazy_static = "1.4" +rand = "0.8.5" +tokio = { version = "1.20", features = ["rt"] } diff --git a/examples/tide/Cargo.toml b/examples/tide/Cargo.toml new file mode 100644 index 0000000..0a75208 --- /dev/null +++ b/examples/tide/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tide" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-mongodb-session = { path = "../../" } +tide = "0.17.0-beta.1" +async-std = "1.10" diff --git a/examples/mongodb_session_store.rs b/examples/tide/src/main.rs similarity index 95% rename from examples/mongodb_session_store.rs rename to examples/tide/src/main.rs index 355040e..c3aa3a7 100644 --- a/examples/mongodb_session_store.rs +++ b/examples/tide/src/main.rs @@ -1,6 +1,3 @@ -extern crate async_mongodb_session; -extern crate tide; - use async_mongodb_session::MongodbSessionStore; #[async_std::main] diff --git a/src/lib.rs b/src/lib.rs index 37ec7e7..2c7e771 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,21 +13,19 @@ #![forbid(unsafe_code, future_incompatible, rust_2018_idioms)] #![deny(missing_debug_implementations, nonstandard_style)] -#![warn(missing_docs, missing_doc_code_examples, unreachable_pub)] +#![warn(missing_docs, rustdoc::missing_doc_code_examples, unreachable_pub)] use async_session::chrono::{Duration, Utc}; use async_session::{async_trait, Result, Session, SessionStore}; -use mongodb::bson; -use mongodb::bson::{doc, Bson}; +use mongodb::bson::{self, doc, Bson, Document}; use mongodb::options::{ReplaceOptions, SelectionCriteria}; use mongodb::Client; /// A MongoDB session store. #[derive(Debug, Clone)] pub struct MongodbSessionStore { - client: mongodb::Client, - db: String, - coll_name: String, + collection: mongodb::Collection, + database: mongodb::Database, } impl MongodbSessionStore { @@ -40,9 +38,9 @@ impl MongodbSessionStore { /// .await?; /// # Ok(()) }) } /// ``` - pub async fn new(uri: &str, db: &str, coll_name: &str) -> mongodb::error::Result { + pub async fn new(uri: &str, db_name: &str, coll_name: &str) -> mongodb::error::Result { let client = Client::with_uri_str(uri).await?; - let middleware = Self::from_client(client, db, coll_name); + let middleware = Self::from_client(client, db_name, coll_name); middleware.create_expire_index("expireAt", 0).await?; Ok(middleware) } @@ -66,16 +64,17 @@ impl MongodbSessionStore { /// let store = MongodbSessionStore::from_client(client, "db_name", "collection"); /// # Ok(()) }) } /// ``` - pub fn from_client(client: Client, db: &str, coll_name: &str) -> Self { + pub fn from_client(client: Client, db_name: &str, coll_name: &str) -> Self { + let database = client.database(db_name); + let collection = database.collection(coll_name); Self { - client, - db: db.to_string(), - coll_name: coll_name.to_string(), + database, + collection, } } /// Initialize the default expiration mechanism, based on the document expiration - /// that mongodb provides https://docs.mongodb.com/manual/tutorial/expire-data/#expire-documents-at-a-specific-clock-time. + /// that mongodb provides . /// The default ttl applyed to sessions without expiry is 20 minutes. /// If the `expireAt` date field contains a date in the past, mongodb considers the document expired and will be deleted. /// Note: mongodb runs the expiration logic every 60 seconds. @@ -89,8 +88,7 @@ impl MongodbSessionStore { /// # Ok(()) }) } /// ``` pub async fn initialize(&self) -> Result { - &self.index_on_expiry_at().await?; - Ok(()) + self.index_on_expiry_at().await } /// private associated function @@ -102,7 +100,7 @@ impl MongodbSessionStore { expire_after_seconds: u32, ) -> mongodb::error::Result<()> { let create_index = doc! { - "createIndexes": &self.coll_name, + "createIndexes": self.collection.name(), "indexes": [ { "key" : { field_name: 1 }, @@ -111,8 +109,7 @@ impl MongodbSessionStore { } ] }; - self.client - .database(&self.db) + self.database .run_command( create_index, SelectionCriteria::ReadPreference(mongodb::options::ReadPreference::Primary), @@ -123,7 +120,7 @@ impl MongodbSessionStore { /// Create a new index for the `expireAt` property, allowing to expire sessions at a specific clock time. /// If the `expireAt` date field contains a date in the past, mongodb considers the document expired and will be deleted. - /// https://docs.mongodb.com/manual/tutorial/expire-data/#expire-documents-at-a-specific-clock-time + /// /// ```rust /// # fn main() -> async_session::Result { async_std::task::block_on(async { /// # use async_mongodb_session::MongodbSessionStore; @@ -142,14 +139,13 @@ impl MongodbSessionStore { #[async_trait] impl SessionStore for MongodbSessionStore { async fn store_session(&self, session: Session) -> Result> { - let coll = self.client.database(&self.db).collection(&self.coll_name); + let coll = &self.collection; let value = bson::to_bson(&session)?; let id = session.id(); let query = doc! { "session_id": id }; let expire_at = match session.expiry() { None => Utc::now() + Duration::from_std(std::time::Duration::from_secs(1200)).unwrap(), - Some(expiry) => *{ expiry }, }; let replacement = doc! { "session_id": id, "session": value, "expireAt": expire_at, "created": Utc::now() }; @@ -162,7 +158,7 @@ impl SessionStore for MongodbSessionStore { async fn load_session(&self, cookie_value: String) -> Result> { let id = Session::id_from_cookie_value(&cookie_value)?; - let coll = self.client.database(&self.db).collection(&self.coll_name); + let coll = &self.collection; let filter = doc! { "session_id": id }; match coll.find_one(filter, None).await? { None => Ok(None), @@ -175,7 +171,7 @@ impl SessionStore for MongodbSessionStore { // https://docs.mongodb.com/manual/core/index-ttl/#timing-of-the-delete-operation // This prevents those documents being returned if let Some(expiry_at) = doc.get("expireAt").and_then(Bson::as_datetime) { - if expiry_at < &Utc::now() { + if expiry_at.to_chrono() < Utc::now() { return Ok(None); } } @@ -185,14 +181,14 @@ impl SessionStore for MongodbSessionStore { } async fn destroy_session(&self, session: Session) -> Result { - let coll = self.client.database(&self.db).collection(&self.coll_name); + let coll = &self.collection; coll.delete_one(doc! { "session_id": session.id() }, None) .await?; Ok(()) } async fn clear_store(&self) -> Result { - let coll = self.client.database(&self.db).collection(&self.coll_name); + let coll = &self.collection; coll.drop(None).await?; self.initialize().await?; Ok(()) diff --git a/tests/test.rs b/tests/test.rs index 16379c3..5cd0a30 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,5 +1,18 @@ #[cfg(test)] mod tests { + use std::future::Future; + + #[cfg(feature = "async-std-runtime")] + fn run_test(future: F) -> F::Output { + async_std::task::block_on(future) + } + + #[cfg(feature = "tokio-runtime")] + fn run_test(future: F) -> F::Output { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(future) + } + use async_mongodb_session::*; use async_session::{Session, SessionStore}; use lazy_static::lazy_static; @@ -10,110 +23,119 @@ mod tests { lazy_static! { static ref HOST: String = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); static ref PORT: String = env::var("PORT").unwrap_or_else(|_| "27017".to_string()); + static ref DATABASE: String = + env::var("DATABASE").unwrap_or_else(|_| "db_name".to_string()); + static ref COLLECTION: String = + env::var("COLLECTION").unwrap_or_else(|_| "collection".to_string()); static ref CONNECTION_STRING: String = format!("mongodb://{}:{}/", HOST.as_str(), PORT.as_str()); } - #[test] - fn test_from_client() -> async_session::Result { - async_std::task::block_on(async { - let client_options = match ClientOptions::parse(&CONNECTION_STRING).await { - Ok(c) => c, - Err(e) => panic!("Client Options Failed: {}", e), - }; - - let client = match Client::with_options(client_options) { - Ok(c) => c, - Err(e) => panic!("Client Creation Failed: {}", e), - }; - - let store = MongodbSessionStore::from_client(client, "db_name", "collection"); - let mut rng = rand::thread_rng(); - let n2: u16 = rng.gen(); - let key = format!("key-{}", n2); - let value = format!("value-{}", n2); - let mut session = Session::new(); - session.insert(&key, &value)?; - - let cookie_value = store.store_session(session).await?.unwrap(); - let session = store.load_session(cookie_value).await?.unwrap(); - assert_eq!(&session.get::(&key).unwrap(), &value); - - Ok(()) - }) + async fn from_client() -> async_session::Result { + let client_options = match ClientOptions::parse(CONNECTION_STRING.as_str()).await { + Ok(c) => c, + Err(e) => panic!("Client Options Failed: {}", e), + }; + + let client = match Client::with_options(client_options) { + Ok(c) => c, + Err(e) => panic!("Client Creation Failed: {}", e), + }; + + let store = MongodbSessionStore::from_client(client, &DATABASE, &COLLECTION); + let mut rng = rand::thread_rng(); + let n2: u16 = rng.gen(); + let key = format!("key-{}", n2); + let value = format!("value-{}", n2); + let mut session = Session::new(); + session.insert(&key, &value)?; + + let cookie_value = store.store_session(session).await?.unwrap(); + let session = store.load_session(cookie_value).await?.unwrap(); + assert_eq!(&session.get::(&key).unwrap(), &value); + + Ok(()) } - #[test] - fn test_new() -> async_session::Result { - async_std::task::block_on(async { - let store = - MongodbSessionStore::new(&CONNECTION_STRING, "db_name", "collection").await?; - - let mut rng = rand::thread_rng(); - let n2: u16 = rng.gen(); - let key = format!("key-{}", n2); - let value = format!("value-{}", n2); - let mut session = Session::new(); - session.insert(&key, &value)?; - - let cookie_value = store.store_session(session).await?.unwrap(); - let session = store.load_session(cookie_value).await?.unwrap(); - assert_eq!(&session.get::(&key).unwrap(), &value); - - Ok(()) - }) + async fn new() -> async_session::Result { + let store = MongodbSessionStore::new(&CONNECTION_STRING, &DATABASE, &COLLECTION).await?; + + let mut rng = rand::thread_rng(); + let n2: u16 = rng.gen(); + let key = format!("key-{}", n2); + let value = format!("value-{}", n2); + let mut session = Session::new(); + session.insert(&key, &value)?; + + let cookie_value = store.store_session(session).await?.unwrap(); + let session = store.load_session(cookie_value).await?.unwrap(); + assert_eq!(&session.get::(&key).unwrap(), &value); + + Ok(()) } - #[test] - fn test_with_expire() -> async_session::Result { - async_std::task::block_on(async { - let store = - MongodbSessionStore::new(&CONNECTION_STRING, "db_name", "collection").await?; - - store.initialize().await?; - - let mut rng = rand::thread_rng(); - let n2: u16 = rng.gen(); - let key = format!("key-{}", n2); - let value = format!("value-{}", n2); - let mut session = Session::new(); - session.expire_in(std::time::Duration::from_secs(5)); - session.insert(&key, &value)?; - - let cookie_value = store.store_session(session).await?.unwrap(); - let session = store.load_session(cookie_value).await?.unwrap(); - assert_eq!(&session.get::(&key).unwrap(), &value); - - Ok(()) - }) + async fn with_expire() -> async_session::Result { + let store = MongodbSessionStore::new(&CONNECTION_STRING, &DATABASE, &COLLECTION).await?; + + store.initialize().await?; + + let mut rng = rand::thread_rng(); + let n2: u16 = rng.gen(); + let key = format!("key-{}", n2); + let value = format!("value-{}", n2); + let mut session = Session::new(); + session.expire_in(std::time::Duration::from_secs(5)); + session.insert(&key, &value)?; + + let cookie_value = store.store_session(session).await?.unwrap(); + let session = store.load_session(cookie_value).await?.unwrap(); + assert_eq!(&session.get::(&key).unwrap(), &value); + + Ok(()) } - #[test] - fn test_check_expired() -> async_session::Result { + async fn check_expired() -> async_session::Result { use async_std::task; use std::time::Duration; - async_std::task::block_on(async { - let store = - MongodbSessionStore::new(&CONNECTION_STRING, "db_name", "collection").await?; + let store = MongodbSessionStore::new(&CONNECTION_STRING, &DATABASE, &COLLECTION).await?; + + store.initialize().await?; + + let mut rng = rand::thread_rng(); + let n2: u16 = rng.gen(); + let key = format!("key-{}", n2); + let value = format!("value-{}", n2); + let mut session = Session::new(); + session.expire_in(Duration::from_secs(1)); + session.insert(&key, &value)?; + + let cookie_value = store.store_session(session).await?.unwrap(); - store.initialize().await?; + task::sleep(Duration::from_secs(1)).await; + let session_to_recover = store.load_session(cookie_value).await?; - let mut rng = rand::thread_rng(); - let n2: u16 = rng.gen(); - let key = format!("key-{}", n2); - let value = format!("value-{}", n2); - let mut session = Session::new(); - session.expire_in(Duration::from_secs(1)); - session.insert(&key, &value)?; + assert!(&session_to_recover.is_none()); - let cookie_value = store.store_session(session).await?.unwrap(); + Ok(()) + } + + #[test] + fn test_from_client() -> async_session::Result { + run_test(from_client()) + } - task::sleep(Duration::from_secs(1)).await; - let session_to_recover = store.load_session(cookie_value).await?; + #[test] + fn test_new() -> async_session::Result { + run_test(new()) + } - assert!(&session_to_recover.is_none()); + #[test] + fn test_with_expire() -> async_session::Result { + run_test(with_expire()) + } - Ok(()) - }) + #[test] + fn test_check_expired() -> async_session::Result { + run_test(check_expired()) } }