Skip to main content

syrup_rail_postgres/
transactions.rs

1#![warn(missing_docs)]
2
3use std::{error::Error, fmt, time::Duration};
4
5use async_trait::async_trait;
6use sqlx::PgConnection;
7use syrup_rail::{BillingEvent, BillingEventSubject};
8
9use crate::host_error::{BoxError, RedactedHostErrorSource};
10
11/// Value-redacted failure returned by the host transaction coordinator.
12#[derive(Debug)]
13pub struct BillingTransactionError {
14    source: RedactedHostErrorSource,
15}
16
17impl BillingTransactionError {
18    /// Wraps a host transaction error without exposing its value through
19    /// ordinary formatting or the standard error-source chain.
20    pub fn new(source: impl Error + Send + Sync + 'static) -> Self {
21        Self {
22            source: RedactedHostErrorSource::new(source),
23        }
24    }
25
26    /// Returns the host error for explicit application-level inspection.
27    ///
28    /// Consuming this wrapper is the only boundary that reveals the arbitrary
29    /// host source.
30    pub fn into_source(self) -> BoxError {
31        self.source.into_inner()
32    }
33}
34
35impl fmt::Display for BillingTransactionError {
36    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
37        formatter.write_str("billing transaction operation failed")
38    }
39}
40
41impl Error for BillingTransactionError {}
42
43/// Value-redacted failure returned while appending a host outbox event.
44#[derive(Debug)]
45pub struct BillingEventWriteError {
46    source: RedactedHostErrorSource,
47}
48
49impl BillingEventWriteError {
50    /// Wraps a host outbox error without exposing its value through ordinary
51    /// formatting or the standard error-source chain.
52    pub fn new(source: impl Error + Send + Sync + 'static) -> Self {
53        Self {
54            source: RedactedHostErrorSource::new(source),
55        }
56    }
57
58    /// Returns the host error for explicit application-level inspection.
59    ///
60    /// Consuming this wrapper is the only boundary that reveals the arbitrary
61    /// host source.
62    pub fn into_source(self) -> BoxError {
63        self.source.into_inner()
64    }
65}
66
67impl fmt::Display for BillingEventWriteError {
68    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
69        formatter.write_str("billing event append failed")
70    }
71}
72
73impl Error for BillingEventWriteError {}
74
75/// Durable availability of the host recipient locked for a billing event.
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub enum BillingTransactionSubjectState {
78    /// The host recipient remains live and may receive ordinary mutations.
79    LiveRecipient,
80    /// The host retained the billing subject only for financial history.
81    RetainedSubject,
82}
83
84/// Host-prepared transaction whose subject authorization lock is acquired
85/// before any shared billing lock.
86#[async_trait]
87pub trait BillingTransactionCoordinator: Send + Sync {
88    /// Begins a host transaction and locks the exact event subject before any
89    /// shared billing row lock is acquired.
90    async fn begin(
91        &self,
92        subject: BillingEventSubject,
93        lock_timeout: Duration,
94    ) -> Result<Box<dyn BillingTransaction>, BillingTransactionError>;
95}
96
97/// One host-owned transaction and its typed event projection capability.
98///
99/// The returned connection is the same transaction on which `append_event`
100/// and `commit` operate. Implementations must not acquire another connection.
101#[async_trait]
102pub trait BillingTransaction: Send {
103    /// Returns the connection owned by this exact host transaction.
104    fn connection(&mut self) -> &mut PgConnection;
105
106    /// Returns whether the locked host subject is live or retained.
107    fn subject_state(&self) -> BillingTransactionSubjectState;
108
109    /// Appends a typed billing event to the host outbox on this transaction.
110    async fn append_event(&mut self, event: &BillingEvent) -> Result<(), BillingEventWriteError>;
111
112    /// Commits both host and shared billing changes.
113    async fn commit(self: Box<Self>) -> Result<(), BillingTransactionError>;
114
115    /// Rolls back both host and shared billing changes.
116    async fn rollback(self: Box<Self>) -> Result<(), BillingTransactionError>;
117}