use crate::{IsolationLevel, Propagation, TransactionError, TransactionManager, TransactionResult};
use std::sync::Arc;
#[derive(Clone)]
pub struct TransactionTemplate {
manager: Arc<dyn TransactionManager>,
propagation: Propagation,
isolation: IsolationLevel,
read_only: bool,
timeout_secs: Option<u64>,
}
impl std::fmt::Debug for TransactionTemplate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransactionTemplate")
.field("propagation", &self.propagation)
.field("isolation", &self.isolation)
.field("read_only", &self.read_only)
.field("timeout_secs", &self.timeout_secs)
.finish()
}
}
impl TransactionTemplate {
pub fn new(manager: Arc<dyn TransactionManager>) -> Self {
Self {
manager,
propagation: Propagation::default(),
isolation: IsolationLevel::default(),
read_only: false,
timeout_secs: None,
}
}
pub fn propagation(mut self, propagation: Propagation) -> Self {
self.propagation = propagation;
self
}
pub fn isolation(mut self, isolation: IsolationLevel) -> Self {
self.isolation = isolation;
self
}
pub fn read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self
}
pub fn timeout_secs(mut self, timeout: u64) -> Self {
self.timeout_secs = Some(timeout);
self
}
pub async fn execute<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 mut def = crate::manager::TransactionDefinition::new("template")
.propagation(self.propagation)
.isolation(self.isolation)
.read_only(self.read_only);
if let Some(timeout) = self.timeout_secs {
def.timeout_secs = Some(timeout);
}
let status = self.manager.begin(&def).await?;
let result = f().await;
match result {
Ok(value) => {
self.manager.commit(status).await?;
Ok(value)
},
Err(e) => {
self.manager.rollback(status).await?;
Err(e.into())
},
}
}
pub async fn execute_without_result<F, E>(&self, f: F) -> TransactionResult<()>
where
F: FnOnce() -> futures::future::BoxFuture<'static, Result<(), E>> + Send + Sync,
E: Into<TransactionError> + Send + 'static,
{
self.execute(f).await
}
pub async fn execute_result<F, T, E>(&self, f: F) -> Result<T, E>
where
F: FnOnce() -> futures::future::BoxFuture<'static, Result<T, E>> + Send + Sync,
T: Send + 'static,
E: Into<TransactionError> + Send + 'static,
TransactionError: Into<E>,
{
self.execute(f).await.map_err(Into::into)
}
}
pub(crate) trait TransactionCallback<T>: Send {
fn execute(&self) -> futures::future::BoxFuture<'_, TransactionResult<T>>;
}
pub(crate) trait TransactionCallbackWithoutResult: Send {
fn execute(&self) -> futures::future::BoxFuture<'_, TransactionResult<()>>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manager::SimpleTransactionManager;
#[tokio::test]
async fn test_transaction_template() {
let manager = Arc::new(SimpleTransactionManager::new());
let template = TransactionTemplate::new(manager);
let result = template
.execute(|| Box::pin(async { Ok::<_, TransactionError>(42) }))
.await
.unwrap();
assert_eq!(result, 42);
}
#[tokio::test]
async fn test_transaction_template_rollback() {
let manager = Arc::new(SimpleTransactionManager::new());
let template = TransactionTemplate::new(manager);
let result = template
.execute(|| {
Box::pin(async {
Err::<(), TransactionError>(TransactionError::CommitFailed("error".to_string()))
})
})
.await;
assert!(result.is_err());
}
}