From cb71792a125e9522cc926e72e89f5b221562b4fa Mon Sep 17 00:00:00 2001 From: sttk Date: Sat, 23 Aug 2025 21:54:22 +0900 Subject: [PATCH 1/3] doc,comment,test: added the usage of RedisDataSrc and RedisDataConn --- README.md | 102 ++++++++++++++++++++++++++++++++++ src/standalone.rs | 86 ++++++++++++++++++++++++++-- tests/standalone_sync_test.rs | 68 +++++++++++++++++++++++ 3 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 tests/standalone_sync_test.rs diff --git a/README.md b/README.md index 7847658..9ef74f0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,108 @@ The sabi data access library for Redis in Rust. +`sabi_redis` is a Rust crate that provides a streamlined way to access various Redis configurations +within the sabi framework. It includes `DataSrc` and `DataConn` derived classes designed to make +your development process more efficient + +`RedisDataSrc` and `RedisDataConn` are designed for a standalone Redis server and provide +synchronous connections for processing Redis commands. + +Unlike relational databases, Redis does not support data rollbacks. This can lead to data +inconsistency if a transaction involving both Redis and another database fails mid-process. +To address this, `sabi_redis` offers three unique features to help developers manage Redis updates +and revert changes when necessary: *"force back"*, *"pre-commit"*, and *"post-commit"*. + +## Installation + +In Cargo.toml, write this crate as a dependency: + +```toml +[dependencies] +sabi_redis = "0.0.0" +``` + +## Usage + +### For Standalone Server And Synchronous Commands + +Here is an example of how to use `RedisDataSrc` and `RedisDataConn` to connect to Redis and +execute a simple command. + +```rust +use errs; +use override_macro::{overridable, override_with}; +use redis::TypedCommands; +use sabi; +use sabi_redis::{RedisDataSrc, RedisDataConn}; + +fn main() -> Result<(), errs::Err> { + // Register a `RedisDataSrc` instance to connect to a Redis server with the key "redis". + sabi::uses("redis", RedisDataSrc::new("redis://127.0.0.1:6379/0")); + + // In this setup process, the registered `RedisDataSrc` instance cnnects to a Redis server. + let _auto_shutdown = sabi::setup()?; + + my_app() +} + +fn my_app() -> Result<(), errs::Err> { + let mut data = sabi::DataHub::new(); + sabi::txn!(my_logic, data) +} + +fn my_logic(data: &mut impl MyData) -> Result<(), errs::Err> { + let greeting = data.get_greeting()?; + data.say_greeting(&greeting) +} + +#[overridable] +trait MyData { + fn get_greeting(&mut self) -> Result; + fn say_greeting(&mut self, greeting: &str) -> Result<(), errs::Err>; +} + +#[overridable] +trait GettingDataAcc: sabi::DataAcc { + fn get_greeting(&mut self) -> Result { + Ok("Hello!".to_string()) + } +} + +#[overridable] +trait RedisSayingDataAcc: sabi::DataAcc { + fn say_greeting(&mut self, greeting: &str) -> Result<(), errs::Err> { + // Retrieve a `RedisDataConn` instance by the key "redis". + let data_conn = self.get_data_conn::("redis")?; + + // Get a Redis connection to execute Redis synchronous commands. + let mut redis_conn = data_conn.get_connection()?; + + if let Err(e) = redis_conn.set("greeting", greeting) { + return Err(errs::Err::with_source("fail to set gretting", e)); + } + + // Register a force back process to revert updates to Redis when an error occurs. + data_conn.add_force_back(|redis_conn| { + let result = redis_conn.del("greeting"); + if let Err(e) = result { + return Err(errs::Err::with_source("fail to force back", e)); + } + Ok(()) + }); + + Ok(()) + } +} + +impl GettingDataAcc for sabi::DataHub {} +impl RedisSayingDataAcc for sabi::DataHub {} + +#[override_with(GettingDataAcc, RedisSayingDataAcc)] +impl MyData for sabi::DataHub {} +``` + + ## Supported Rust versions This crate supports Rust 1.85.1 or later. diff --git a/src/standalone.rs b/src/standalone.rs index 40e058c..183ab76 100644 --- a/src/standalone.rs +++ b/src/standalone.rs @@ -43,6 +43,81 @@ pub enum RedisDataSrcError { /// The `add_force_back` method allows registering functions to manually revert changes /// if an error occurs during a multi-step process or a transaction involving /// other external data sources. +/// +/// ## Example +/// +/// ```rust +/// use errs; +/// use override_macro::{overridable, override_with}; +/// use redis::TypedCommands; +/// use sabi; +/// use sabi_redis::{RedisDataSrc, RedisDataConn}; +/// +/// fn main() -> Result<(), errs::Err> { +/// // Register a `RedisDataSrc` instance to connect to a Redis server with the key "redis". +/// sabi::uses("redis", RedisDataSrc::new("redis://127.0.0.1:6379/0")); +/// +/// // In this setup process, the registered `RedisDataSrc` instance cnnects to a Redis server. +/// let _auto_shutdown = sabi::setup()?; +/// +/// my_app() +/// } +/// +/// fn my_app() -> Result<(), errs::Err> { +/// let mut data = sabi::DataHub::new(); +/// sabi::txn!(my_logic, data) +/// } +/// +/// fn my_logic(data: &mut impl MyData) -> Result<(), errs::Err> { +/// let greeting = data.get_greeting()?; +/// data.say_greeting(&greeting) +/// } +/// +/// #[overridable] +/// trait MyData { +/// fn get_greeting(&mut self) -> Result; +/// fn say_greeting(&mut self, greeting: &str) -> Result<(), errs::Err>; +/// } +/// +/// #[overridable] +/// trait GettingDataAcc: sabi::DataAcc { +/// fn get_greeting(&mut self) -> Result { +/// Ok("Hello!".to_string()) +/// } +/// } +/// +/// #[overridable] +/// trait RedisSayingDataAcc: sabi::DataAcc { +/// fn say_greeting(&mut self, greeting: &str) -> Result<(), errs::Err> { +/// // Retrieve a `RedisDataConn` instance by the key "redis". +/// let data_conn = self.get_data_conn::("redis")?; +/// +/// // Get a Redis connection to execute Redis synchronous commands. +/// let mut redis_conn = data_conn.get_connection()?; +/// +/// if let Err(e) = redis_conn.set("greeting", greeting) { +/// return Err(errs::Err::with_source("fail to set gretting", e)); +/// } +/// +/// // Register a force back process to revert updates to Redis when an error occurs. +/// data_conn.add_force_back(|redis_conn| { +/// let result = redis_conn.del("greeting"); +/// if let Err(e) = result { +/// return Err(errs::Err::with_source("fail to force back", e)); +/// } +/// Ok(()) +/// }); +/// +/// Ok(()) +/// } +/// } +/// +/// impl GettingDataAcc for sabi::DataHub {} +/// impl RedisSayingDataAcc for sabi::DataHub {} +/// +/// #[override_with(GettingDataAcc, RedisSayingDataAcc)] +/// impl MyData for sabi::DataHub {} +/// ``` pub struct RedisDataConn { pool: r2d2::Pool, pre_commit_vec: Vec Result<(), Err>>>, @@ -75,7 +150,7 @@ impl RedisDataConn { /// finished, and right before their commit processes are made, pub fn add_pre_commit(&mut self, f: F) where - F: FnMut(&mut redis::Connection) -> Result<(), Err> + 'static + F: FnMut(&mut redis::Connection) -> Result<(), Err> + 'static, { self.pre_commit_vec.push(Box::new(f)); } @@ -85,7 +160,7 @@ impl RedisDataConn { /// The provided function will be called as post-transaction processes. pub fn add_post_commit(&mut self, f: F) where - F: FnMut(&mut redis::Connection) -> Result<(), Err> + 'static + F: FnMut(&mut redis::Connection) -> Result<(), Err> + 'static, { self.post_commit_vec.push(Box::new(f)); } @@ -96,7 +171,7 @@ impl RedisDataConn { /// due to a failure in a subsequent operation. pub fn add_force_back(&mut self, f: F) where - F: FnMut(&mut redis::Connection) -> Result<(), Err> + 'static + F: FnMut(&mut redis::Connection) -> Result<(), Err> + 'static, { self.force_back_vec.push(Box::new(f)); } @@ -117,7 +192,10 @@ impl DataConn for RedisDataConn { } Ok(()) } - Err(e) => Err(Err::with_source(RedisDataSrcError::FailToGetConnectionFromPool, e)), + Err(e) => Err(Err::with_source( + RedisDataSrcError::FailToGetConnectionFromPool, + e, + )), } } diff --git a/tests/standalone_sync_test.rs b/tests/standalone_sync_test.rs new file mode 100644 index 0000000..ed32c7e --- /dev/null +++ b/tests/standalone_sync_test.rs @@ -0,0 +1,68 @@ +#[cfg(test)] +mod integration_tests_of_sabi_redis { + use errs; + use override_macro::{overridable, override_with}; + use redis::TypedCommands; + use sabi; + use sabi_redis::{RedisDataConn, RedisDataSrc}; + + #[test] + fn test() -> Result<(), errs::Err> { + sabi::uses("redis", RedisDataSrc::new("redis://127.0.0.1:6379/10")); + + let _auto_shutdown = sabi::setup()?; + + my_app() + } + + fn my_app() -> Result<(), errs::Err> { + let mut data = sabi::DataHub::new(); + sabi::txn!(my_logic, data) + } + + fn my_logic(data: &mut impl MyData) -> Result<(), errs::Err> { + let greeting = data.get_greeting()?; + data.say_greeting(&greeting) + } + + #[overridable] + trait MyData { + fn get_greeting(&mut self) -> Result; + fn say_greeting(&mut self, greeting: &str) -> Result<(), errs::Err>; + } + + #[overridable] + trait GettingDataAcc: sabi::DataAcc { + fn get_greeting(&mut self) -> Result { + Ok("Hello!".to_string()) + } + } + + #[overridable] + trait RedisSayingDataAcc: sabi::DataAcc { + fn say_greeting(&mut self, greeting: &str) -> Result<(), errs::Err> { + let data_conn = self.get_data_conn::("redis")?; + let mut redis_conn = data_conn.get_connection()?; + + if let Err(e) = redis_conn.set("greeting", greeting) { + return Err(errs::Err::with_source("fail to set gretting", e)); + } + + data_conn.add_force_back(|redis_conn| { + let result = redis_conn.del("greeting"); + if let Err(e) = result { + return Err(errs::Err::with_source("fail to force back", e)); + } + Ok(()) + }); + + Ok(()) + } + } + + impl GettingDataAcc for sabi::DataHub {} + impl RedisSayingDataAcc for sabi::DataHub {} + + #[override_with(GettingDataAcc, RedisSayingDataAcc)] + impl MyData for sabi::DataHub {} +} From f236681112214aee3a1b65547eb4fb836caabdbf Mon Sep 17 00:00:00 2001 From: sttk Date: Sat, 23 Aug 2025 22:35:19 +0900 Subject: [PATCH 2/3] fix: modified what pointed out --- README.md | 4 ++-- src/standalone.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ef74f0..73c0c84 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ fn main() -> Result<(), errs::Err> { // Register a `RedisDataSrc` instance to connect to a Redis server with the key "redis". sabi::uses("redis", RedisDataSrc::new("redis://127.0.0.1:6379/0")); - // In this setup process, the registered `RedisDataSrc` instance cnnects to a Redis server. + // In this setup process, the registered `RedisDataSrc` instance connects to a Redis server. let _auto_shutdown = sabi::setup()?; my_app() @@ -80,7 +80,7 @@ trait RedisSayingDataAcc: sabi::DataAcc { let mut redis_conn = data_conn.get_connection()?; if let Err(e) = redis_conn.set("greeting", greeting) { - return Err(errs::Err::with_source("fail to set gretting", e)); + return Err(errs::Err::with_source("fail to set greeting", e)); } // Register a force back process to revert updates to Redis when an error occurs. diff --git a/src/standalone.rs b/src/standalone.rs index 183ab76..1424c4b 100644 --- a/src/standalone.rs +++ b/src/standalone.rs @@ -57,7 +57,7 @@ pub enum RedisDataSrcError { /// // Register a `RedisDataSrc` instance to connect to a Redis server with the key "redis". /// sabi::uses("redis", RedisDataSrc::new("redis://127.0.0.1:6379/0")); /// -/// // In this setup process, the registered `RedisDataSrc` instance cnnects to a Redis server. +/// // In this setup process, the registered `RedisDataSrc` instance connects to a Redis server. /// let _auto_shutdown = sabi::setup()?; /// /// my_app() @@ -96,7 +96,7 @@ pub enum RedisDataSrcError { /// let mut redis_conn = data_conn.get_connection()?; /// /// if let Err(e) = redis_conn.set("greeting", greeting) { -/// return Err(errs::Err::with_source("fail to set gretting", e)); +/// return Err(errs::Err::with_source("fail to set greeting", e)); /// } /// /// // Register a force back process to revert updates to Redis when an error occurs. From 77467afe66dbd4f372a0347a3af91264075d4b16 Mon Sep 17 00:00:00 2001 From: sttk Date: Sat, 23 Aug 2025 22:37:25 +0900 Subject: [PATCH 3/3] fix: modified what pointed out --- tests/standalone_sync_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/standalone_sync_test.rs b/tests/standalone_sync_test.rs index ed32c7e..9220941 100644 --- a/tests/standalone_sync_test.rs +++ b/tests/standalone_sync_test.rs @@ -45,7 +45,7 @@ mod integration_tests_of_sabi_redis { let mut redis_conn = data_conn.get_connection()?; if let Err(e) = redis_conn.set("greeting", greeting) { - return Err(errs::Err::with_source("fail to set gretting", e)); + return Err(errs::Err::with_source("fail to set greeting", e)); } data_conn.add_force_back(|redis_conn| {