use std::collections::BTreeMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::error::CoolError;
use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuditOperation {
Create,
Update,
Delete,
}
impl AuditOperation {
pub const fn as_str(&self) -> &'static str {
match self {
AuditOperation::Create => "create",
AuditOperation::Update => "update",
AuditOperation::Delete => "delete",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct AuditActor {
pub id: Option<String>,
pub claims: BTreeMap<String, Value>,
pub ip: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AuditEvent {
pub event_id: uuid::Uuid,
pub schema_name: String,
pub model: String,
pub operation: AuditOperation,
pub primary_key: serde_json::Value,
pub actor: AuditActor,
pub tenant: Option<String>,
pub before: Option<serde_json::Value>,
pub after: Option<serde_json::Value>,
pub request_id: Option<String>,
pub occurred_at: chrono::DateTime<chrono::Utc>,
}
#[async_trait::async_trait]
pub trait AuditSink: Send + Sync + 'static {
async fn record(&self, event: &AuditEvent) -> Result<(), CoolError>;
}
#[derive(Debug, Clone, Default)]
pub struct NoopAuditSink;
#[async_trait::async_trait]
impl AuditSink for NoopAuditSink {
async fn record(&self, _event: &AuditEvent) -> Result<(), CoolError> {
Ok(())
}
}
pub struct MulticastAuditSink {
sinks: Vec<Arc<dyn AuditSink>>,
}
impl MulticastAuditSink {
pub fn new(sinks: Vec<Arc<dyn AuditSink>>) -> Self {
Self { sinks }
}
}
#[async_trait::async_trait]
impl AuditSink for MulticastAuditSink {
async fn record(&self, event: &AuditEvent) -> Result<(), CoolError> {
let mut errors = Vec::new();
for sink in &self.sinks {
if let Err(error) = sink.record(event).await {
errors.push(error);
}
}
if errors.is_empty() {
Ok(())
} else {
Err(CoolError::Internal(format!(
"{} audit sink(s) failed: {}",
errors.len(),
errors
.iter()
.map(|e| e.detail().unwrap_or("(no detail)").to_owned())
.collect::<Vec<_>>()
.join("; "),
)))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransactionIsolation {
ReadCommitted,
RepeatableRead,
Serializable,
}
impl TransactionIsolation {
pub fn parse(value: &str) -> Result<Self, CoolError> {
match value.trim().to_ascii_lowercase().as_str() {
"read_committed" | "read committed" => Ok(Self::ReadCommitted),
"repeatable_read" | "repeatable read" => Ok(Self::RepeatableRead),
"serializable" => Ok(Self::Serializable),
other => Err(CoolError::Validation(format!(
"unknown transaction isolation level '{other}'; expected one of \
'read_committed', 'repeatable_read', 'serializable'",
))),
}
}
pub const fn as_sql(&self) -> &'static str {
match self {
Self::ReadCommitted => "READ COMMITTED",
Self::RepeatableRead => "REPEATABLE READ",
Self::Serializable => "SERIALIZABLE",
}
}
}