1use std::collections::BTreeMap;
12use std::sync::Arc;
13
14use serde::{Deserialize, Serialize};
15
16use crate::error::CoolError;
17use crate::value::Value;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum AuditOperation {
22 Create,
23 Update,
24 Delete,
25}
26
27impl AuditOperation {
28 pub const fn as_str(&self) -> &'static str {
29 match self {
30 AuditOperation::Create => "create",
31 AuditOperation::Update => "update",
32 AuditOperation::Delete => "delete",
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
38pub struct AuditActor {
39 pub id: Option<String>,
43 pub claims: BTreeMap<String, Value>,
47 pub ip: Option<String>,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct AuditEvent {
53 pub event_id: uuid::Uuid,
54 pub schema_name: String,
58 pub model: String,
60 pub operation: AuditOperation,
61 pub primary_key: serde_json::Value,
62 pub actor: AuditActor,
63 pub tenant: Option<String>,
67 pub before: Option<serde_json::Value>,
68 pub after: Option<serde_json::Value>,
69 pub request_id: Option<String>,
72 pub occurred_at: chrono::DateTime<chrono::Utc>,
73}
74
75#[async_trait::async_trait]
81pub trait AuditSink: Send + Sync + 'static {
82 async fn record(&self, event: &AuditEvent) -> Result<(), CoolError>;
83}
84
85#[derive(Debug, Clone, Default)]
89pub struct NoopAuditSink;
90
91#[async_trait::async_trait]
92impl AuditSink for NoopAuditSink {
93 async fn record(&self, _event: &AuditEvent) -> Result<(), CoolError> {
94 Ok(())
95 }
96}
97
98pub struct MulticastAuditSink {
103 sinks: Vec<Arc<dyn AuditSink>>,
104}
105
106impl MulticastAuditSink {
107 pub fn new(sinks: Vec<Arc<dyn AuditSink>>) -> Self {
108 Self { sinks }
109 }
110}
111
112#[async_trait::async_trait]
113impl AuditSink for MulticastAuditSink {
114 async fn record(&self, event: &AuditEvent) -> Result<(), CoolError> {
115 let mut errors = Vec::new();
116 for sink in &self.sinks {
117 if let Err(error) = sink.record(event).await {
118 errors.push(error);
119 }
120 }
121 if errors.is_empty() {
122 Ok(())
123 } else {
124 Err(CoolError::Internal(format!(
125 "{} audit sink(s) failed: {}",
126 errors.len(),
127 errors
128 .iter()
129 .map(|e| e.detail().unwrap_or("(no detail)").to_owned())
130 .collect::<Vec<_>>()
131 .join("; "),
132 )))
133 }
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
143pub enum TransactionIsolation {
144 ReadCommitted,
145 RepeatableRead,
146 Serializable,
147}
148
149impl TransactionIsolation {
150 pub fn parse(value: &str) -> Result<Self, CoolError> {
151 match value.trim().to_ascii_lowercase().as_str() {
152 "read_committed" | "read committed" => Ok(Self::ReadCommitted),
153 "repeatable_read" | "repeatable read" => Ok(Self::RepeatableRead),
154 "serializable" => Ok(Self::Serializable),
155 other => Err(CoolError::Validation(format!(
156 "unknown transaction isolation level '{other}'; expected one of \
157 'read_committed', 'repeatable_read', 'serializable'",
158 ))),
159 }
160 }
161
162 pub const fn as_sql(&self) -> &'static str {
163 match self {
164 Self::ReadCommitted => "READ COMMITTED",
165 Self::RepeatableRead => "REPEATABLE READ",
166 Self::Serializable => "SERIALIZABLE",
167 }
168 }
169}