diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..8af59dd --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUST_TEST_THREADS = "1" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ca8335f..f1b240f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -57,30 +57,23 @@ jobs: toolchain: ${{ matrix.rust }} override: true - - name: check avoid-dev-deps - uses: actions-rs/cargo@v1 - if: matrix.rust == 'nightly' - with: - command: check - args: --all -Z avoid-dev-deps - - name: pg tests uses: actions-rs/cargo@v1 with: command: test - args: --all --features pg,async_std -- --test-threads=1 + args: --all --features pg -- --test-threads=1 - name: sqlite tests uses: actions-rs/cargo@v1 with: command: test - args: --all --features sqlite,async_std + args: --all --features sqlite - name: mysql tests uses: actions-rs/cargo@v1 with: command: test - args: --all --features mysql,async_std -- --test-threads=1 + args: --all --features mysql -- --test-threads=1 check_fmt_and_docs: name: Checking fmt, clippy, and docs diff --git a/Cargo.toml b/Cargo.toml index b452a5d..3a21b0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,25 +12,29 @@ keywords = ["sessions", "sqlx", "sqlite", "postgres", "mysql"] categories = ["web-programming::http-server", "web-programming", "database"] [package.metadata.docs.rs] -features = ["pg", "sqlite", "mysql", "async_std"] +features = ["pg", "sqlite", "mysql"] [features] -default = ["native-tls"] +default = [] sqlite = ["sqlx/sqlite"] pg = ["sqlx/postgres", "sqlx/json"] native-tls = ["sqlx/runtime-async-std-native-tls"] rustls = ["sqlx/runtime-async-std-rustls"] -async_std = ["async-std"] mysql = ["sqlx/mysql", "sqlx/json"] [dependencies] -async-session = "3.0.0" -sqlx = { version = "0.6.2", features = ["chrono"] } -async-std = { version = "1.12.0", optional = true } +async-session = { git = "https://github.com/http-rs/async-session", branch = "overhaul-session-and-session-store", default-features = false } +sqlx = { version = "0.6.2", features = ["time"] } +log = "0.4.17" +serde_json = "1.0.93" +serde = "1.0.152" +thiserror = "1.0.38" +time = "0.3.18" +base64 = "0.21.0" [dev-dependencies] async-std = { version = "1.12.0", features = ["attributes"] } [dev-dependencies.sqlx] version = "0.6.2" -features = ["chrono", "runtime-async-std-native-tls"] +features = ["runtime-async-std-native-tls"] diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..ffadb8a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,17 @@ +/// Errors that can arise in the operation of the session stores +/// included in this crate +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum Error { + /// an error that comes from sqlx + #[error(transparent)] + SqlxError(#[from] sqlx::Error), + + /// an error that comes from serde_json + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), + + /// an error that comes from base64 + #[error(transparent)] + Base64(#[from] base64::DecodeError), +} diff --git a/src/lib.rs b/src/lib.rs index 3fff7c2..ed73a55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,3 +54,6 @@ pub use pg::PostgresSessionStore; mod mysql; #[cfg(feature = "mysql")] pub use mysql::MySqlSessionStore; + +mod error; +pub use error::Error; diff --git a/src/mysql.rs b/src/mysql.rs index 0edf82c..db768a5 100644 --- a/src/mysql.rs +++ b/src/mysql.rs @@ -1,19 +1,18 @@ -use async_session::{async_trait, chrono::Utc, log, serde_json, Result, Session, SessionStore}; +use async_session::{async_trait, Session, SessionStore}; use sqlx::{pool::PoolConnection, Executor, MySql, MySqlPool}; +use time::OffsetDateTime; /// sqlx mysql session store for async-sessions /// /// ```rust -/// use async_sqlx_session::MySqlSessionStore; +/// use async_sqlx_session::{MySqlSessionStore, Error}; /// use async_session::{Session, SessionStore}; /// use std::time::Duration; /// -/// # fn main() -> async_session::Result { async_std::task::block_on(async { +/// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await?; /// store.migrate().await?; /// # store.clear_store().await?; -/// # #[cfg(feature = "async_std")] -/// store.spawn_cleanup_task(Duration::from_secs(60 * 60)); /// /// let mut session = Session::new(); /// session.insert("key", vec![1,2,3]); @@ -36,9 +35,8 @@ impl MySqlSessionStore { /// with [`with_table_name`](crate::MySqlSessionStore::with_table_name). /// /// ```rust - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let pool = sqlx::MySqlPool::connect(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await.unwrap(); /// let store = MySqlSessionStore::from_client(pool) /// .with_table_name("custom_table_name"); @@ -60,9 +58,8 @@ impl MySqlSessionStore { /// [`new_with_table_name`](crate::MySqlSessionStore::new_with_table_name) /// /// ```rust - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await?; /// store.migrate().await; /// # Ok(()) }) } @@ -80,9 +77,8 @@ impl MySqlSessionStore { /// [`new_with_table_name`](crate::MySqlSessionStore::new_with_table_name) /// /// ```rust - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new_with_table_name(&std::env::var("MYSQL_TEST_DB_URL").unwrap(), "custom_table_name").await?; /// store.migrate().await; /// # Ok(()) }) } @@ -94,9 +90,8 @@ impl MySqlSessionStore { /// Chainable method to add a custom table name. This will panic /// if the table name is not `[a-zA-Z0-9_-]`. /// ```rust - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await? /// .with_table_name("custom_name"); /// store.migrate().await; @@ -104,9 +99,8 @@ impl MySqlSessionStore { /// ``` /// /// ```should_panic - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await? /// .with_table_name("johnny (); drop users;"); /// # Ok(()) }) } @@ -134,9 +128,9 @@ impl MySqlSessionStore { /// exactly-once modifications to the schema of the session table /// on breaking releases. /// ```rust - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::{Result, SessionStore, Session}; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await?; /// # store.clear_store().await?; /// store.migrate().await?; @@ -176,55 +170,19 @@ impl MySqlSessionStore { self.client.acquire().await } - /// Spawns an async_std::task that clears out stale (expired) - /// sessions on a periodic basis. Only available with the - /// async_std feature enabled. - /// - /// ```rust,no_run - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::{Result, SessionStore, Session}; - /// # use std::time::Duration; - /// # fn main() -> Result { async_std::task::block_on(async { - /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await?; - /// store.migrate().await?; - /// # let join_handle = - /// store.spawn_cleanup_task(Duration::from_secs(1)); - /// let mut session = Session::new(); - /// session.expire_in(Duration::from_secs(0)); - /// store.store_session(session).await?; - /// assert_eq!(store.count().await?, 1); - /// async_std::task::sleep(Duration::from_secs(2)).await; - /// assert_eq!(store.count().await?, 0); - /// # join_handle.cancel().await; - /// # Ok(()) }) } - /// ``` - #[cfg(feature = "async_std")] - pub fn spawn_cleanup_task( - &self, - period: std::time::Duration, - ) -> async_std::task::JoinHandle<()> { - let store = self.clone(); - async_std::task::spawn(async move { - loop { - async_std::task::sleep(period).await; - if let Err(error) = store.cleanup().await { - log::error!("cleanup error: {}", error); - } - } - }) - } - /// Performs a one-time cleanup task that clears out stale /// (expired) sessions. You may want to call this from cron. /// ```rust - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::{chrono::{Utc,Duration}, Result, SessionStore, Session}; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; + /// # use std::time::Duration; + /// # use time::OffsetDateTime; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await?; /// store.migrate().await?; /// # store.clear_store().await?; /// let mut session = Session::new(); - /// session.set_expiry(Utc::now() - Duration::seconds(5)); + /// session.set_expiry(OffsetDateTime::now_utc() - Duration::from_secs(5)); /// store.store_session(session).await?; /// assert_eq!(store.count().await?, 1); /// store.cleanup().await?; @@ -234,7 +192,7 @@ impl MySqlSessionStore { pub async fn cleanup(&self) -> sqlx::Result<()> { let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name("DELETE FROM %%TABLE_NAME%% WHERE expires < ?")) - .bind(Utc::now()) + .bind(OffsetDateTime::now_utc()) .execute(&mut connection) .await?; @@ -245,10 +203,10 @@ impl MySqlSessionStore { /// expired sessions /// /// ```rust - /// # use async_sqlx_session::MySqlSessionStore; - /// # use async_session::{Result, SessionStore, Session}; + /// # use async_sqlx_session::{MySqlSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; /// # use std::time::Duration; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()).await?; /// store.migrate().await?; /// # store.clear_store().await?; @@ -270,26 +228,34 @@ impl MySqlSessionStore { #[async_trait] impl SessionStore for MySqlSessionStore { - async fn load_session(&self, cookie_value: String) -> Result> { + type Error = crate::Error; + + async fn load_session(&self, cookie_value: String) -> Result, Self::Error> { let id = Session::id_from_cookie_value(&cookie_value)?; let mut connection = self.connection().await?; - let result: Option<(String,)> = sqlx::query_as(&self.substitute_table_name( - "SELECT session FROM %%TABLE_NAME%% WHERE id = ? AND (expires IS NULL OR expires > ?)", + let result: Option<(String, Option)> = sqlx::query_as(&self.substitute_table_name( + "SELECT session, expires FROM %%TABLE_NAME%% WHERE id = ? AND (expires IS NULL OR expires > ?)", )) .bind(&id) - .bind(Utc::now()) + .bind(OffsetDateTime::now_utc()) .fetch_optional(&mut connection) .await?; - Ok(result - .map(|(session,)| serde_json::from_str(&session)) - .transpose()?) + if let Some((data, expiry)) = result { + Ok(Some(Session::from_parts( + id, + serde_json::from_str(&data)?, + expiry, + ))) + } else { + Ok(None) + } } - async fn store_session(&self, session: Session) -> Result> { + async fn store_session(&self, session: Session) -> Result, Self::Error> { let id = session.id(); - let string = serde_json::to_string(&session)?; + let string = serde_json::to_string(session.data())?; let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name( @@ -310,7 +276,7 @@ impl SessionStore for MySqlSessionStore { Ok(session.into_cookie_value()) } - async fn destroy_session(&self, session: Session) -> Result { + async fn destroy_session(&self, session: Session) -> Result<(), Self::Error> { let id = session.id(); let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name("DELETE FROM %%TABLE_NAME%% WHERE id = ?")) @@ -321,7 +287,7 @@ impl SessionStore for MySqlSessionStore { Ok(()) } - async fn clear_store(&self) -> Result { + async fn clear_store(&self) -> Result<(), Self::Error> { let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name("TRUNCATE %%TABLE_NAME%%")) .execute(&mut connection) @@ -334,8 +300,9 @@ impl SessionStore for MySqlSessionStore { #[cfg(test)] mod tests { use super::*; - use async_session::chrono::DateTime; - use std::time::Duration; + use serde_json::{json, Value}; + use std::{collections::HashMap, time::Duration}; + use time::OffsetDateTime; async fn test_store() -> MySqlSessionStore { let store = MySqlSessionStore::new(&std::env::var("MYSQL_TEST_DB_URL").unwrap()) @@ -353,14 +320,14 @@ mod tests { } #[async_std::test] - async fn creating_a_new_session_with_no_expiry() -> Result { + async fn creating_a_new_session_with_no_expiry() -> Result<(), crate::Error> { let store = test_store().await; let mut session = Session::new(); session.insert("key", "value")?; let cloned = session.clone(); let cookie_value = store.store_session(session).await?.unwrap(); - let (id, expires, serialized, count): (String, Option>, String, i64) = + let (id, expires, serialized, count): (String, Option, String, i64) = sqlx::query_as("select id, expires, session, (select count(*) from async_sessions) from async_sessions") .fetch_one(&mut store.connection().await?) .await?; @@ -369,9 +336,11 @@ mod tests { assert_eq!(id, cloned.id()); assert_eq!(expires, None); - let deserialized_session: Session = serde_json::from_str(&serialized)?; - assert_eq!(cloned.id(), deserialized_session.id()); - assert_eq!("value", &deserialized_session.get::("key").unwrap()); + let deserialized_session: HashMap = serde_json::from_str(&serialized)?; + assert_eq!( + &json!("value"), + deserialized_session.get(&String::from("key")).unwrap() + ); let loaded_session = store.load_session(cookie_value).await?.unwrap(); assert_eq!(cloned.id(), loaded_session.id()); @@ -382,7 +351,7 @@ mod tests { } #[async_std::test] - async fn updating_a_session() -> Result { + async fn updating_a_session() -> Result<(), crate::Error> { let store = test_store().await; let mut session = Session::new(); let original_id = session.id().to_owned(); @@ -409,7 +378,7 @@ mod tests { } #[async_std::test] - async fn updating_a_session_extending_expiry() -> Result { + async fn updating_a_session_extending_expiry() -> Result<(), crate::Error> { let store = test_store().await; let mut session = Session::new(); session.expire_in(Duration::from_secs(10)); @@ -418,49 +387,57 @@ mod tests { let cookie_value = store.store_session(session).await?.unwrap(); let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); - assert_eq!(session.expiry().unwrap(), &original_expires); + assert_eq!( + session.expiry().unwrap().unix_timestamp(), + original_expires.unix_timestamp() + ); session.expire_in(Duration::from_secs(20)); let new_expires = session.expiry().unwrap().clone(); store.store_session(session).await?; let session = store.load_session(cookie_value.clone()).await?.unwrap(); - assert_eq!(session.expiry().unwrap(), &new_expires); + assert_eq!( + session.expiry().unwrap().unix_timestamp(), + new_expires.unix_timestamp() + ); - let (id, expires, count): (String, DateTime, i64) = sqlx::query_as( + let (id, expires, count): (String, Option, i64) = sqlx::query_as( "select id, expires, (select count(*) from async_sessions) from async_sessions", ) .fetch_one(&mut store.connection().await?) .await?; assert_eq!(1, count); - assert_eq!(expires.timestamp_millis(), new_expires.timestamp_millis()); + assert_eq!( + expires.unwrap().unix_timestamp(), + new_expires.unix_timestamp() + ); assert_eq!(original_id, id); Ok(()) } #[async_std::test] - async fn creating_a_new_session_with_expiry() -> Result { + async fn creating_a_new_session_with_expiry() -> Result<(), crate::Error> { let store = test_store().await; let mut session = Session::new(); - session.expire_in(Duration::from_secs(1)); + session.expire_in(std::time::Duration::from_secs(1)); session.insert("key", "value")?; let cloned = session.clone(); let cookie_value = store.store_session(session).await?.unwrap(); - let (id, expires, serialized, count): (String, Option>, String, i64) = + let (id, expires, serialized, count): (String, Option, String, i64) = sqlx::query_as("select id, expires, session, (select count(*) from async_sessions) from async_sessions") .fetch_one(&mut store.connection().await?) .await?; assert_eq!(1, count); assert_eq!(id, cloned.id()); - assert!(expires.unwrap() > Utc::now()); + assert!(expires.unwrap() > OffsetDateTime::now_utc()); - let deserialized_session: Session = serde_json::from_str(&serialized)?; - assert_eq!(cloned.id(), deserialized_session.id()); - assert_eq!("value", &deserialized_session.get::("key").unwrap()); + let deserialized_session: HashMap = serde_json::from_str(&serialized)?; + assert_eq!(&json!("value"), deserialized_session.get("key").unwrap()); let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap(); assert_eq!(cloned.id(), loaded_session.id()); @@ -468,14 +445,14 @@ mod tests { assert!(!loaded_session.is_expired()); - async_std::task::sleep(Duration::from_secs(1)).await; + async_std::task::sleep(std::time::Duration::from_secs(1)).await; assert_eq!(None, store.load_session(cookie_value).await?); Ok(()) } #[async_std::test] - async fn destroying_a_single_session() -> Result { + async fn destroying_a_single_session() -> Result<(), crate::Error> { let store = test_store().await; for _ in 0..3i8 { store.store_session(Session::new()).await?; @@ -494,7 +471,7 @@ mod tests { } #[async_std::test] - async fn clearing_the_whole_store() -> Result { + async fn clearing_the_whole_store() -> Result<(), crate::Error> { let store = test_store().await; for _ in 0..3i8 { store.store_session(Session::new()).await?; diff --git a/src/pg.rs b/src/pg.rs index 6cdf4a6..c6bc180 100644 --- a/src/pg.rs +++ b/src/pg.rs @@ -1,20 +1,18 @@ -use async_session::{async_trait, chrono::Utc, log, serde_json, Result, Session, SessionStore}; +use async_session::{async_trait, Session, SessionStore}; use sqlx::{pool::PoolConnection, Executor, PgPool, Postgres}; +use time::OffsetDateTime; /// sqlx postgres session store for async-sessions /// /// ```rust -/// use async_sqlx_session::PostgresSessionStore; +/// use async_sqlx_session::{PostgresSessionStore, Error}; /// use async_session::{Session, SessionStore}; /// use std::time::Duration; /// -/// # fn main() -> async_session::Result { async_std::task::block_on(async { +/// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await?; /// store.migrate().await?; /// # store.clear_store().await?; -/// # #[cfg(feature = "async_std")] { -/// store.spawn_cleanup_task(Duration::from_secs(60 * 60)); -/// # } /// /// let mut session = Session::new(); /// session.insert("key", vec![1,2,3]); @@ -37,9 +35,8 @@ impl PostgresSessionStore { /// with [`with_table_name`](crate::PostgresSessionStore::with_table_name). /// /// ```rust - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let pool = sqlx::PgPool::connect(&std::env::var("PG_TEST_DB_URL").unwrap()).await.unwrap(); /// let store = PostgresSessionStore::from_client(pool) /// .with_table_name("custom_table_name"); @@ -61,9 +58,8 @@ impl PostgresSessionStore { /// [`new_with_table_name`](crate::PostgresSessionStore::new_with_table_name) /// /// ```rust - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await?; /// store.migrate().await; /// # Ok(()) }) } @@ -81,9 +77,8 @@ impl PostgresSessionStore { /// [`new_with_table_name`](crate::PostgresSessionStore::new_with_table_name) /// /// ```rust - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new_with_table_name(&std::env::var("PG_TEST_DB_URL").unwrap(), "custom_table_name").await?; /// store.migrate().await; /// # Ok(()) }) } @@ -95,9 +90,8 @@ impl PostgresSessionStore { /// Chainable method to add a custom table name. This will panic /// if the table name is not `[a-zA-Z0-9_-]+`. /// ```rust - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await? /// .with_table_name("custom_name"); /// store.migrate().await; @@ -105,9 +99,8 @@ impl PostgresSessionStore { /// ``` /// /// ```should_panic - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await? /// .with_table_name("johnny (); drop users;"); /// # Ok(()) }) } @@ -135,9 +128,9 @@ impl PostgresSessionStore { /// exactly-once modifications to the schema of the session table /// on breaking releases. /// ```rust - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::{Result, SessionStore, Session}; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await?; /// # store.clear_store().await?; /// store.migrate().await?; @@ -173,56 +166,18 @@ impl PostgresSessionStore { self.client.acquire().await } - /// Spawns an async_std::task that clears out stale (expired) - /// sessions on a periodic basis. Only available with the - /// async_std feature enabled. - /// - /// ```rust,no_run - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::{Result, SessionStore, Session}; - /// # use std::time::Duration; - /// # fn main() -> Result { async_std::task::block_on(async { - /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await?; - /// store.migrate().await?; - /// # let join_handle = - /// store.spawn_cleanup_task(Duration::from_secs(1)); - /// let mut session = Session::new(); - /// session.expire_in(Duration::from_secs(0)); - /// store.store_session(session).await?; - /// assert_eq!(store.count().await?, 1); - /// async_std::task::sleep(Duration::from_secs(2)).await; - /// assert_eq!(store.count().await?, 0); - /// # join_handle.cancel().await; - /// # Ok(()) }) } - /// ``` - #[cfg(feature = "async_std")] - pub fn spawn_cleanup_task( - &self, - period: std::time::Duration, - ) -> async_std::task::JoinHandle<()> { - use async_std::task; - let store = self.clone(); - task::spawn(async move { - loop { - task::sleep(period).await; - if let Err(error) = store.cleanup().await { - log::error!("cleanup error: {}", error); - } - } - }) - } - /// Performs a one-time cleanup task that clears out stale /// (expired) sessions. You may want to call this from cron. /// ```rust - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::{chrono::{Utc,Duration}, Result, SessionStore, Session}; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; + /// # use time::{OffsetDateTime, Duration}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await?; /// store.migrate().await?; /// # store.clear_store().await?; /// let mut session = Session::new(); - /// session.set_expiry(Utc::now() - Duration::seconds(5)); + /// session.set_expiry(OffsetDateTime::now_utc() - Duration::seconds(5)); /// store.store_session(session).await?; /// assert_eq!(store.count().await?, 1); /// store.cleanup().await?; @@ -232,7 +187,7 @@ impl PostgresSessionStore { pub async fn cleanup(&self) -> sqlx::Result<()> { let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name("DELETE FROM %%TABLE_NAME%% WHERE expires < $1")) - .bind(Utc::now()) + .bind(OffsetDateTime::now_utc()) .execute(&mut connection) .await?; @@ -243,10 +198,10 @@ impl PostgresSessionStore { /// expired sessions /// /// ```rust - /// # use async_sqlx_session::PostgresSessionStore; - /// # use async_session::{Result, SessionStore, Session}; + /// # use async_sqlx_session::{PostgresSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; /// # use std::time::Duration; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = PostgresSessionStore::new(&std::env::var("PG_TEST_DB_URL").unwrap()).await?; /// store.migrate().await?; /// # store.clear_store().await?; @@ -268,26 +223,33 @@ impl PostgresSessionStore { #[async_trait] impl SessionStore for PostgresSessionStore { - async fn load_session(&self, cookie_value: String) -> Result> { + type Error = crate::Error; + + async fn load_session(&self, cookie_value: String) -> Result, Self::Error> { let id = Session::id_from_cookie_value(&cookie_value)?; let mut connection = self.connection().await?; - let result: Option<(String,)> = sqlx::query_as(&self.substitute_table_name( - "SELECT session FROM %%TABLE_NAME%% WHERE id = $1 AND (expires IS NULL OR expires > $2)" + let result: Option<(String, Option)> = sqlx::query_as(&self.substitute_table_name( + "SELECT session, expires FROM %%TABLE_NAME%% WHERE id = $1 AND (expires IS NULL OR expires > $2)" )) .bind(&id) - .bind(Utc::now()) + .bind(OffsetDateTime::now_utc()) .fetch_optional(&mut connection) .await?; - Ok(result - .map(|(session,)| serde_json::from_str(&session)) - .transpose()?) + match result { + Some((data, expiry)) => Ok(Some(Session::from_parts( + id, + serde_json::from_str(&data)?, + expiry, + ))), + None => Ok(None), + } } - async fn store_session(&self, session: Session) -> Result> { + async fn store_session(&self, session: Session) -> Result, Self::Error> { let id = session.id(); - let string = serde_json::to_string(&session)?; + let string = serde_json::to_string(session.data())?; let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name( @@ -308,7 +270,7 @@ impl SessionStore for PostgresSessionStore { Ok(session.into_cookie_value()) } - async fn destroy_session(&self, session: Session) -> Result { + async fn destroy_session(&self, session: Session) -> Result<(), Self::Error> { let id = session.id(); let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name("DELETE FROM %%TABLE_NAME%% WHERE id = $1")) @@ -319,7 +281,7 @@ impl SessionStore for PostgresSessionStore { Ok(()) } - async fn clear_store(&self) -> Result { + async fn clear_store(&self) -> Result<(), Self::Error> { let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name("TRUNCATE %%TABLE_NAME%%")) .execute(&mut connection) @@ -331,8 +293,12 @@ impl SessionStore for PostgresSessionStore { #[cfg(test)] mod tests { + use serde_json::Value; + use std::collections::HashMap; + + use crate::Error; + use super::*; - use async_session::chrono::DateTime; use std::time::Duration; async fn test_store() -> PostgresSessionStore { @@ -351,14 +317,14 @@ mod tests { } #[async_std::test] - async fn creating_a_new_session_with_no_expiry() -> Result { + async fn creating_a_new_session_with_no_expiry() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); session.insert("key", "value")?; let cloned = session.clone(); let cookie_value = store.store_session(session).await?.unwrap(); - let (id, expires, serialized, count): (String, Option>, String, i64) = + let (id, expires, serialized, count): (String, Option, String, i64) = sqlx::query_as("select id, expires, session, (select count(*) from async_sessions) from async_sessions") .fetch_one(&mut store.connection().await?) .await?; @@ -367,9 +333,11 @@ mod tests { assert_eq!(id, cloned.id()); assert_eq!(expires, None); - let deserialized_session: Session = serde_json::from_str(&serialized)?; - assert_eq!(cloned.id(), deserialized_session.id()); - assert_eq!("value", &deserialized_session.get::("key").unwrap()); + let deserialized_session: HashMap = serde_json::from_str(&serialized)?; + assert_eq!( + "\"value\"", + &deserialized_session.get("key").unwrap().to_string() + ); let loaded_session = store.load_session(cookie_value).await?.unwrap(); assert_eq!(cloned.id(), loaded_session.id()); @@ -380,7 +348,7 @@ mod tests { } #[async_std::test] - async fn updating_a_session() -> Result { + async fn updating_a_session() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); let original_id = session.id().to_owned(); @@ -407,38 +375,48 @@ mod tests { } #[async_std::test] - async fn updating_a_session_extending_expiry() -> Result { + async fn updating_a_session_extending_expiry() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); session.expire_in(Duration::from_secs(10)); let original_id = session.id().to_owned(); let original_expires = session.expiry().unwrap().clone(); + let cookie_value = store.store_session(session).await?.unwrap(); let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); - assert_eq!(session.expiry().unwrap(), &original_expires); + assert_eq!( + session.expiry().unwrap().unix_timestamp(), + original_expires.unix_timestamp() + ); session.expire_in(Duration::from_secs(20)); let new_expires = session.expiry().unwrap().clone(); store.store_session(session).await?; let session = store.load_session(cookie_value.clone()).await?.unwrap(); - assert_eq!(session.expiry().unwrap(), &new_expires); + assert_eq!( + session.expiry().unwrap().unix_timestamp(), + new_expires.unix_timestamp() + ); - let (id, expires, count): (String, DateTime, i64) = sqlx::query_as( + let (id, expires, count): (String, Option, i64) = sqlx::query_as( "select id, expires, (select count(*) from async_sessions) from async_sessions", ) .fetch_one(&mut store.connection().await?) .await?; assert_eq!(1, count); - assert_eq!(expires.timestamp_millis(), new_expires.timestamp_millis()); + assert_eq!( + expires.unwrap().unix_timestamp(), + new_expires.unix_timestamp() + ); assert_eq!(original_id, id); Ok(()) } #[async_std::test] - async fn creating_a_new_session_with_expiry() -> Result { + async fn creating_a_new_session_with_expiry() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); session.expire_in(Duration::from_secs(1)); @@ -447,18 +425,23 @@ mod tests { let cookie_value = store.store_session(session).await?.unwrap(); - let (id, expires, serialized, count): (String, Option>, String, i64) = + let (id, expires, serialized, count): (String, Option, String, i64) = sqlx::query_as("select id, expires, session, (select count(*) from async_sessions) from async_sessions") .fetch_one(&mut store.connection().await?) .await?; assert_eq!(1, count); assert_eq!(id, cloned.id()); - assert!(expires.unwrap() > Utc::now()); + assert!(expires.unwrap() > OffsetDateTime::now_utc()); - let deserialized_session: Session = serde_json::from_str(&serialized)?; - assert_eq!(cloned.id(), deserialized_session.id()); - assert_eq!("value", &deserialized_session.get::("key").unwrap()); + let deserialized_session: HashMap = serde_json::from_str(&serialized)?; + assert_eq!( + "\"value\"", + deserialized_session + .get(&String::from("key")) + .unwrap() + .to_string() + ); let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap(); assert_eq!(cloned.id(), loaded_session.id()); @@ -473,7 +456,7 @@ mod tests { } #[async_std::test] - async fn destroying_a_single_session() -> Result { + async fn destroying_a_single_session() -> Result<(), Error> { let store = test_store().await; for _ in 0..3i8 { store.store_session(Session::new()).await?; @@ -492,7 +475,7 @@ mod tests { } #[async_std::test] - async fn clearing_the_whole_store() -> Result { + async fn clearing_the_whole_store() -> Result<(), Error> { let store = test_store().await; for _ in 0..3i8 { store.store_session(Session::new()).await?; diff --git a/src/sqlite.rs b/src/sqlite.rs index 77e973f..d3f3cc3 100644 --- a/src/sqlite.rs +++ b/src/sqlite.rs @@ -1,18 +1,18 @@ -use async_session::{async_trait, chrono::Utc, log, serde_json, Result, Session, SessionStore}; +use crate::Error; +use async_session::{async_trait, Session, SessionStore}; use sqlx::{pool::PoolConnection, sqlite::SqlitePool, Sqlite}; +use time::OffsetDateTime; /// sqlx sqlite session store for async-sessions /// /// ```rust -/// use async_sqlx_session::SqliteSessionStore; +/// use async_sqlx_session::{SqliteSessionStore, Error}; /// use async_session::{Session, SessionStore}; /// use std::time::Duration; /// -/// # fn main() -> async_session::Result { async_std::task::block_on(async { +/// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new("sqlite::memory:").await?; /// store.migrate().await?; -/// # #[cfg(feature = "async_std")] -/// store.spawn_cleanup_task(Duration::from_secs(60 * 60)); /// /// let mut session = Session::new(); /// session.insert("key", vec![1,2,3]); @@ -35,9 +35,8 @@ impl SqliteSessionStore { /// with [`with_table_name`](crate::SqliteSessionStore::with_table_name). /// /// ```rust - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap(); /// let store = SqliteSessionStore::from_client(pool) /// .with_table_name("custom_table_name"); @@ -62,9 +61,8 @@ impl SqliteSessionStore { /// [`new_with_table_name`](crate::SqliteSessionStore::new_with_table_name) /// /// ```rust - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new("sqlite::memory:").await?; /// store.migrate().await; /// # Ok(()) }) } @@ -81,9 +79,8 @@ impl SqliteSessionStore { /// [`new_with_table_name`](crate::SqliteSessionStore::new_with_table_name) /// /// ```rust - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new_with_table_name("sqlite::memory:", "custom_table_name").await?; /// store.migrate().await; /// # Ok(()) }) } @@ -95,9 +92,9 @@ impl SqliteSessionStore { /// Chainable method to add a custom table name. This will panic /// if the table name is not `[a-zA-Z0-9_-]+`. /// ```rust - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new("sqlite::memory:").await? /// .with_table_name("custom_name"); /// store.migrate().await; @@ -105,9 +102,8 @@ impl SqliteSessionStore { /// ``` /// /// ```should_panic - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::Result; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new("sqlite::memory:").await? /// .with_table_name("johnny (); drop users;"); /// # Ok(()) }) } @@ -135,9 +131,9 @@ impl SqliteSessionStore { /// exactly-once modifications to the schema of the session table /// on breaking releases. /// ```rust - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::{Result, SessionStore, Session}; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new("sqlite::memory:").await?; /// assert!(store.count().await.is_err()); /// store.migrate().await?; @@ -175,54 +171,17 @@ impl SqliteSessionStore { self.client.acquire().await } - /// Spawns an async_std::task that clears out stale (expired) - /// sessions on a periodic basis. Only available with the - /// async_std feature enabled. - /// - /// ```rust,no_run - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::{Result, SessionStore, Session}; - /// # use std::time::Duration; - /// # fn main() -> Result { async_std::task::block_on(async { - /// let store = SqliteSessionStore::new("sqlite::memory:").await?; - /// store.migrate().await?; - /// # let join_handle = - /// store.spawn_cleanup_task(Duration::from_secs(1)); - /// let mut session = Session::new(); - /// session.expire_in(Duration::from_secs(0)); - /// store.store_session(session).await?; - /// assert_eq!(store.count().await?, 1); - /// async_std::task::sleep(Duration::from_secs(2)).await; - /// assert_eq!(store.count().await?, 0); - /// # join_handle.cancel().await; - /// # Ok(()) }) } - /// ``` - #[cfg(feature = "async_std")] - pub fn spawn_cleanup_task( - &self, - period: std::time::Duration, - ) -> async_std::task::JoinHandle<()> { - let store = self.clone(); - async_std::task::spawn(async move { - loop { - async_std::task::sleep(period).await; - if let Err(error) = store.cleanup().await { - log::error!("cleanup error: {}", error); - } - } - }) - } - /// Performs a one-time cleanup task that clears out stale /// (expired) sessions. You may want to call this from cron. /// ```rust - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::{chrono::{Utc,Duration}, Result, SessionStore, Session}; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; + /// # use time::{OffsetDateTime, Duration}; + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new("sqlite::memory:").await?; /// store.migrate().await?; /// let mut session = Session::new(); - /// session.set_expiry(Utc::now() - Duration::seconds(5)); + /// session.set_expiry(OffsetDateTime::now_utc() - Duration::seconds(5)); /// store.store_session(session).await?; /// assert_eq!(store.count().await?, 1); /// store.cleanup().await?; @@ -237,7 +196,7 @@ impl SqliteSessionStore { WHERE expires < ? "#, )) - .bind(Utc::now().timestamp()) + .bind(OffsetDateTime::now_utc().unix_timestamp()) .execute(&mut connection) .await?; @@ -248,10 +207,10 @@ impl SqliteSessionStore { /// expired sessions /// /// ```rust - /// # use async_sqlx_session::SqliteSessionStore; - /// # use async_session::{Result, SessionStore, Session}; + /// # use async_sqlx_session::{SqliteSessionStore, Error}; + /// # use async_session::{SessionStore, Session}; /// # use std::time::Duration; - /// # fn main() -> Result { async_std::task::block_on(async { + /// # fn main() -> Result<(), Error> { async_std::task::block_on(async { /// let store = SqliteSessionStore::new("sqlite::memory:").await?; /// store.migrate().await?; /// assert_eq!(store.count().await?, 0); @@ -272,29 +231,38 @@ impl SqliteSessionStore { #[async_trait] impl SessionStore for SqliteSessionStore { - async fn load_session(&self, cookie_value: String) -> Result> { + type Error = Error; + + async fn load_session(&self, cookie_value: String) -> Result, Self::Error> { let id = Session::id_from_cookie_value(&cookie_value)?; let mut connection = self.connection().await?; - let result: Option<(String,)> = sqlx::query_as(&self.substitute_table_name( - r#" - SELECT session FROM %%TABLE_NAME%% + let result: Option<(String, Option)> = + sqlx::query_as(&self.substitute_table_name( + r#" + SELECT session, expires FROM %%TABLE_NAME%% WHERE id = ? AND (expires IS NULL OR expires > ?) "#, - )) - .bind(&id) - .bind(Utc::now().timestamp()) - .fetch_optional(&mut connection) - .await?; + )) + .bind(&id) + .bind(OffsetDateTime::now_utc().unix_timestamp()) + .fetch_optional(&mut connection) + .await?; - Ok(result - .map(|(session,)| serde_json::from_str(&session)) - .transpose()?) + if let Some((data, expiry)) = result { + Ok(Some(Session::from_parts( + id, + serde_json::from_str(&data)?, + expiry, + ))) + } else { + Ok(None) + } } - async fn store_session(&self, session: Session) -> Result> { + async fn store_session(&self, session: Session) -> Result, Self::Error> { let id = session.id(); - let string = serde_json::to_string(&session)?; + let string = serde_json::to_string(session.data())?; let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name( @@ -308,14 +276,14 @@ impl SessionStore for SqliteSessionStore { )) .bind(&id) .bind(&string) - .bind(&session.expiry().map(|expiry| expiry.timestamp())) + .bind(&session.expiry().map(|expiry| expiry.unix_timestamp())) .execute(&mut connection) .await?; Ok(session.into_cookie_value()) } - async fn destroy_session(&self, session: Session) -> Result { + async fn destroy_session(&self, session: Session) -> Result<(), Self::Error> { let id = session.id(); let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name( @@ -330,7 +298,7 @@ impl SessionStore for SqliteSessionStore { Ok(()) } - async fn clear_store(&self) -> Result { + async fn clear_store(&self) -> Result<(), Self::Error> { let mut connection = self.connection().await?; sqlx::query(&self.substitute_table_name( r#" @@ -346,8 +314,10 @@ impl SessionStore for SqliteSessionStore { #[cfg(test)] mod tests { + use serde_json::{json, Value}; + use super::*; - use std::time::Duration; + use std::{collections::HashMap, time::Duration}; async fn test_store() -> SqliteSessionStore { let store = SqliteSessionStore::new("sqlite::memory:") @@ -361,7 +331,7 @@ mod tests { } #[async_std::test] - async fn creating_a_new_session_with_no_expiry() -> Result { + async fn creating_a_new_session_with_no_expiry() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); session.insert("key", "value")?; @@ -377,9 +347,8 @@ mod tests { assert_eq!(id, cloned.id()); assert_eq!(expires, None); - let deserialized_session: Session = serde_json::from_str(&serialized)?; - assert_eq!(cloned.id(), deserialized_session.id()); - assert_eq!("value", &deserialized_session.get::("key").unwrap()); + let deserialized_session: HashMap = serde_json::from_str(&serialized)?; + assert_eq!(&json!("value"), deserialized_session.get("key").unwrap()); let loaded_session = store.load_session(cookie_value).await?.unwrap(); assert_eq!(cloned.id(), loaded_session.id()); @@ -390,7 +359,7 @@ mod tests { } #[async_std::test] - async fn updating_a_session() -> Result { + async fn updating_a_session() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); let original_id = session.id().to_owned(); @@ -416,7 +385,7 @@ mod tests { } #[async_std::test] - async fn updating_a_session_extending_expiry() -> Result { + async fn updating_a_session_extending_expiry() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); session.expire_in(Duration::from_secs(10)); @@ -425,28 +394,34 @@ mod tests { let cookie_value = store.store_session(session).await?.unwrap(); let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); - assert_eq!(session.expiry().unwrap(), &original_expires); + assert_eq!( + session.expiry().unwrap(), + &original_expires.replace_millisecond(0).unwrap() + ); session.expire_in(Duration::from_secs(20)); let new_expires = session.expiry().unwrap().clone(); store.store_session(session).await?; let session = store.load_session(cookie_value.clone()).await?.unwrap(); - assert_eq!(session.expiry().unwrap(), &new_expires); + assert_eq!( + session.expiry().unwrap(), + &new_expires.replace_millisecond(0).unwrap() + ); - let (id, expires, count): (String, i64, i64) = + let (id, expires, count): (String, Option, i64) = sqlx::query_as("select id, expires, count(*) from async_sessions") .fetch_one(&mut store.connection().await?) .await?; assert_eq!(1, count); - assert_eq!(expires, new_expires.timestamp()); + assert_eq!(expires.unwrap(), new_expires.unix_timestamp()); assert_eq!(original_id, id); Ok(()) } #[async_std::test] - async fn creating_a_new_session_with_expiry() -> Result { + async fn creating_a_new_session_with_expiry() -> Result<(), Error> { let store = test_store().await; let mut session = Session::new(); session.expire_in(Duration::from_secs(1)); @@ -462,11 +437,10 @@ mod tests { assert_eq!(1, count); assert_eq!(id, cloned.id()); - assert!(expires.unwrap() > Utc::now().timestamp()); + assert!(expires.unwrap() > OffsetDateTime::now_utc().unix_timestamp()); - let deserialized_session: Session = serde_json::from_str(&serialized)?; - assert_eq!(cloned.id(), deserialized_session.id()); - assert_eq!("value", &deserialized_session.get::("key").unwrap()); + let deserialized_session: HashMap = serde_json::from_str(&serialized)?; + assert_eq!(&json!("value"), deserialized_session.get("key").unwrap()); let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap(); assert_eq!(cloned.id(), loaded_session.id()); @@ -481,7 +455,7 @@ mod tests { } #[async_std::test] - async fn destroying_a_single_session() -> Result { + async fn destroying_a_single_session() -> Result<(), Error> { let store = test_store().await; for _ in 0..3i8 { store.store_session(Session::new()).await?; @@ -500,7 +474,7 @@ mod tests { } #[async_std::test] - async fn clearing_the_whole_store() -> Result { + async fn clearing_the_whole_store() -> Result<(), Error> { let store = test_store().await; for _ in 0..3i8 { store.store_session(Session::new()).await?;