#![allow(async_fn_in_trait)]
use crate::{IsolationLevel, Propagation, TransactionError, TransactionResult};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionalOptions {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub propagation: Option<Propagation>,
#[serde(default)]
pub isolation: Option<IsolationLevel>,
#[serde(default)]
pub timeout_secs: Option<u64>,
#[serde(default = "default_read_only")]
pub read_only: bool,
#[serde(default)]
pub rollback_for: Vec<String>,
#[serde(default)]
pub no_rollback_for: Vec<String>,
}
fn default_read_only() -> bool {
false
}
impl TransactionalOptions {
pub fn new() -> Self {
Self {
name: None,
propagation: None,
isolation: None,
timeout_secs: None,
read_only: false,
rollback_for: Vec::new(),
no_rollback_for: Vec::new(),
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn propagation(mut self, propagation: Propagation) -> Self {
self.propagation = Some(propagation);
self
}
pub fn isolation(mut self, isolation: IsolationLevel) -> Self {
self.isolation = Some(isolation);
self
}
pub fn timeout_secs(mut self, timeout: u64) -> Self {
self.timeout_secs = Some(timeout);
self
}
pub fn read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self
}
pub fn rollback_for(mut self, exception: impl Into<String>) -> Self {
self.rollback_for.push(exception.into());
self
}
pub fn no_rollback_for(mut self, exception: impl Into<String>) -> Self {
self.no_rollback_for.push(exception.into());
self
}
pub fn should_rollback(&self, error: &TransactionError) -> bool {
if self.rollback_for.is_empty() && self.no_rollback_for.is_empty() {
return true;
}
for exception in &self.no_rollback_for {
if error.to_string().contains(exception) {
return false;
}
}
if self.rollback_for.is_empty() {
return true;
}
for exception in &self.rollback_for {
if error.to_string().contains(exception) {
return true;
}
}
false
}
}
impl Default for TransactionalOptions {
fn default() -> Self {
Self::new()
}
}
pub trait Transactional {
async fn in_transaction<F, T, E>(&self, f: F) -> TransactionResult<T>
where
F: FnOnce() -> futures::future::BoxFuture<'static, Result<T, E>> + Send + Sync,
T: Send + 'static,
E: Into<TransactionError> + Send + 'static,
{
let result = f().await;
match result {
Ok(value) => Ok(value),
Err(e) => Err(e.into()),
}
}
async fn in_transaction_with_options<F, T, E>(
&self,
options: &TransactionalOptions,
f: F,
) -> TransactionResult<T>
where
F: FnOnce() -> futures::future::BoxFuture<'static, Result<T, E>> + Send + Sync,
T: Send + 'static,
E: Into<TransactionError> + Send + 'static,
{
let result = f().await;
match result {
Ok(value) => Ok(value),
Err(e) => {
let tx_error = e.into();
if options.should_rollback(&tx_error) {
return Err(tx_error);
}
Err(tx_error)
},
}
}
}
impl<T> Transactional for T where T: Send + Sync {}
pub(crate) struct TransactionGuard<'a> {
status: crate::TransactionStatus,
manager: &'a dyn crate::TransactionManager,
committed: Arc<std::sync::atomic::AtomicBool>,
}
impl<'a> TransactionGuard<'a> {
pub(crate) fn new(
status: crate::TransactionStatus,
manager: &'a dyn crate::TransactionManager,
) -> Self {
Self {
status,
manager,
committed: Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
pub(crate) async fn commit(self) -> TransactionResult<()> {
self.committed
.store(true, std::sync::atomic::Ordering::SeqCst);
self.manager.commit(self.status.clone()).await
}
pub(crate) async fn rollback(self) -> TransactionResult<()> {
self.manager.rollback(self.status.clone()).await
}
pub(crate) fn set_rollback_only(&self) {
self.status.set_rollback_only();
}
}
impl Drop for TransactionGuard<'_> {
fn drop(&mut self) {
if !self.committed.load(std::sync::atomic::Ordering::SeqCst) {
if !self.status.is_completed() {
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transactional_options() {
let options = TransactionalOptions::new()
.name("test_tx")
.propagation(Propagation::RequiresNew)
.isolation(IsolationLevel::Serializable)
.timeout_secs(60)
.read_only(true);
assert_eq!(options.name, Some("test_tx".to_string()));
assert_eq!(options.propagation, Some(Propagation::RequiresNew));
assert!(options.read_only);
}
#[test]
fn test_should_rollback() {
let default_options = TransactionalOptions::new();
let any_error = TransactionError::CommitFailed("Some error".to_string());
assert!(default_options.should_rollback(&any_error));
let options = TransactionalOptions::new().no_rollback_for("Invalid");
let validation_error = TransactionError::InvalidState("Invalid input".to_string());
assert!(!options.should_rollback(&validation_error));
let commit_error = TransactionError::CommitFailed("Connection failed".to_string());
assert!(options.should_rollback(&commit_error));
}
}