Skip to main content

keepsake_sqlx/repository/
types.rs

1use chrono::{DateTime, Utc};
2use keepsake::{AuditEvent, ExpiryPolicy, Keepsake};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[cfg(feature = "postgres")]
7use sqlx::{Row, postgres::PgRow};
8
9/// Keyset cursor for active relation membership scans.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct MembershipCursor {
12    /// Last seen subject kind.
13    pub subject_kind: String,
14    /// Last seen subject id.
15    pub subject_id: String,
16    /// Last seen keepsake id.
17    pub keepsake_id: Uuid,
18}
19
20impl MembershipCursor {
21    /// Creates a cursor positioned after a returned keepsake.
22    #[must_use]
23    pub fn after(keepsake: &Keepsake) -> Self {
24        Self {
25            subject_kind: keepsake.subject().kind().to_owned(),
26            subject_id: keepsake.subject().id().to_owned(),
27            keepsake_id: keepsake.id(),
28        }
29    }
30}
31
32/// A persisted audit event together with its stable storage id.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct AuditEventRecord {
35    /// Monotonic audit row id, stable for keyset pagination.
36    pub id: i64,
37    /// Reconstructed audit event.
38    pub event: AuditEvent,
39}
40
41/// Keyset cursor for audit event reads in stable `(occurred_at, id)` order.
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub struct AuditCursor {
44    /// Last seen occurrence time.
45    pub occurred_at: DateTime<Utc>,
46    /// Last seen audit row id.
47    pub id: i64,
48}
49
50impl AuditCursor {
51    /// Creates a cursor positioned after a returned audit event.
52    #[must_use]
53    pub const fn after(record: &AuditEventRecord) -> Self {
54        Self {
55            occurred_at: record.event.at,
56            id: record.id,
57        }
58    }
59}
60
61/// Keyset cursor for audit outbox export in stable id order.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct AuditOutboxCursor {
64    /// Last seen outbox row id.
65    pub id: i64,
66}
67
68impl AuditOutboxCursor {
69    /// Creates a cursor positioned after a returned outbox record.
70    #[must_use]
71    pub const fn after(record: &AuditOutboxRecord) -> Self {
72        Self { id: record.id }
73    }
74}
75
76/// Audit outbox row for external delivery workers.
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct AuditOutboxRecord {
79    /// Monotonic outbox row id.
80    pub id: i64,
81    /// Source audit event id.
82    pub audit_event_id: i64,
83    /// Stable event type label.
84    pub event_type: String,
85    /// Serialized [`AuditEvent`] payload.
86    pub payload: AuditEvent,
87    /// Worker that currently owns the lease, when claimed.
88    pub claimed_by: Option<String>,
89    /// Lease expiry timestamp, when claimed.
90    pub claimed_until: Option<DateTime<Utc>>,
91    /// Delivery acknowledgement timestamp.
92    pub delivered_at: Option<DateTime<Utc>>,
93}
94
95/// Result of an apply operation.
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct AppliedKeepsake {
98    /// Created keepsake, or the existing active keepsake for duplicate applies.
99    pub keepsake: Keepsake,
100    /// Whether a duplicate active keepsake was prevented.
101    pub duplicate_prevented: bool,
102}
103
104/// Due timed expiry candidate.
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106#[cfg_attr(feature = "postgres", derive(sqlx::FromRow))]
107pub struct TimedExpiryCandidate {
108    /// Keepsake id.
109    pub keepsake_id: Uuid,
110    /// Relation id.
111    pub relation_id: Uuid,
112    /// Subject kind.
113    pub subject_kind: String,
114    /// Subject id.
115    pub subject_id: String,
116    /// Due timestamp.
117    pub due_at: DateTime<Utc>,
118}
119
120/// Due fulfillment expiry candidate.
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct FulfilledExpiryCandidate {
123    /// Keepsake id.
124    pub keepsake_id: Uuid,
125    /// Relation id.
126    pub relation_id: Uuid,
127    /// Subject kind.
128    pub subject_kind: String,
129    /// Subject id.
130    pub subject_id: String,
131    /// Copied expiry policy.
132    pub expiry_policy: ExpiryPolicy,
133}
134
135#[cfg(feature = "postgres")]
136impl<'row> sqlx::FromRow<'row, PgRow> for FulfilledExpiryCandidate {
137    fn from_row(row: &'row PgRow) -> Result<Self, sqlx::Error> {
138        let expiry_policy = serde_json::from_value(row.try_get("expiry_policy")?)
139            .map_err(|error| sqlx::Error::Decode(Box::new(error)))?;
140        Ok(Self {
141            keepsake_id: row.try_get("keepsake_id")?,
142            relation_id: row.try_get("relation_id")?,
143            subject_kind: row.try_get("subject_kind")?,
144            subject_id: row.try_get("subject_id")?,
145            expiry_policy,
146        })
147    }
148}