use crate::{Transaction, TransactionStatus};
use hiver_http::Request;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct TransactionContextExt {
current: Arc<RwLock<Option<Transaction>>>,
stack: Arc<RwLock<Vec<Transaction>>>,
}
impl TransactionContextExt {
pub fn new() -> Self {
Self {
current: Arc::new(RwLock::new(None)),
stack: Arc::new(RwLock::new(Vec::new())),
}
}
pub fn from_request(req: &Request) -> Option<Arc<Self>> {
req.extensions().get::<Arc<Self>>().cloned()
}
pub fn set_to_request(req: &mut Request) -> Arc<Self> {
let ctx = Arc::new(Self::new());
req.extensions_mut().insert(ctx.clone());
ctx
}
pub async fn current_transaction(&self) -> Option<Transaction> {
self.current.read().await.clone()
}
pub async fn set_current_transaction(&self, tx: Transaction) {
let mut current = self.current.write().await;
*current = Some(tx);
}
pub async fn clear(&self) {
let mut current = self.current.write().await;
*current = None;
}
pub async fn push_transaction(&self, tx: Transaction) {
let mut stack = self.stack.write().await;
stack.push(tx);
}
pub async fn pop_transaction(&self) -> Option<Transaction> {
let mut stack = self.stack.write().await;
stack.pop()
}
pub async fn stack_depth(&self) -> usize {
self.stack.read().await.len()
}
pub async fn has_active_transaction(&self) -> bool {
self.current
.read()
.await
.as_ref()
.is_some_and(Transaction::is_active)
}
pub async fn transaction_status(&self) -> Option<TransactionStatus> {
self.current
.read()
.await
.as_ref()
.map(|tx| tx.status().clone())
}
}
impl Default for TransactionContextExt {
fn default() -> Self {
Self::new()
}
}
pub async fn get_transaction_from_request(req: &Request) -> Option<Transaction> {
TransactionContextExt::from_request(req)?
.current_transaction()
.await
}
pub async fn has_active_transaction_in_request(req: &Request) -> bool {
if let Some(ctx) = TransactionContextExt::from_request(req) {
ctx.has_active_transaction().await
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Transaction;
use hiver_http::{Method, Request};
#[tokio::test]
async fn test_transaction_context_ext() {
let mut req = Request::from_method_uri(Method::GET, "/test");
let ctx = TransactionContextExt::set_to_request(&mut req);
let ctx2 = TransactionContextExt::from_request(&req).unwrap();
assert_eq!(Arc::as_ptr(&ctx), Arc::as_ptr(&ctx2));
let tx = Transaction::new("test");
ctx.set_current_transaction(tx.clone()).await;
assert!(ctx.has_active_transaction().await);
let tx_from_req = get_transaction_from_request(&req).await;
assert_eq!(tx_from_req.map(|t| t.status().name().to_string()), Some("test".to_string()));
}
}