1use puerto_core::{DomainEvent, EventEnvelope, RepositoryError};
6use sqlx::{PgConnection, PgPool, Postgres, Transaction};
7
8use crate::map_sqlx_error;
9
10pub struct PgTransaction {
13 tx: Transaction<'static, Postgres>,
14}
15
16impl PgTransaction {
17 pub async fn begin(pool: &PgPool) -> Result<Self, RepositoryError> {
18 let tx = pool.begin().await.map_err(|err| {
19 tracing::error!(error = %err, "unit_of_work: begin failed");
20 map_sqlx_error(&err)
21 })?;
22 Ok(Self { tx })
23 }
24
25 pub fn tx(&mut self) -> &mut PgConnection {
27 &mut self.tx
28 }
29
30 pub async fn record(&mut self, event: &impl DomainEvent) -> Result<(), RepositoryError> {
32 self.record_envelope(&EventEnvelope::new(event)).await
33 }
34
35 pub async fn record_envelope(&mut self, event: &EventEnvelope) -> Result<(), RepositoryError> {
37 let headers = serde_json::to_value(&event.headers).map_err(|err| {
38 tracing::error!(error = %err, event_type = %event.event_type, "unit_of_work: headers not serialisable");
39 RepositoryError::InvalidData
40 })?;
41 sqlx::query(
42 "INSERT INTO outbox \
43 (id, aggregate_type, aggregate_id, event_type, schema_version, payload, headers, occurred_at) \
44 VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
45 )
46 .bind(event.id)
47 .bind(&event.aggregate_type)
48 .bind(event.aggregate_id)
49 .bind(&event.event_type)
50 .bind(i32::from(event.schema_version))
51 .bind(&event.payload)
52 .bind(headers)
53 .bind(event.occurred_at)
54 .execute(&mut *self.tx)
55 .await
56 .map_err(|err| {
57 tracing::error!(error = %err, event_type = %event.event_type, "unit_of_work: outbox insert failed");
58 map_sqlx_error(&err)
59 })?;
60 Ok(())
61 }
62
63 pub async fn commit(self) -> Result<(), RepositoryError> {
64 self.tx.commit().await.map_err(|err| {
65 tracing::error!(error = %err, "unit_of_work: commit failed");
66 map_sqlx_error(&err)
67 })
68 }
69}