use async_trait::async_trait;
use chrono::{DateTime, Utc};
use super::event::AuditEvent;
use crate::error::Error;
#[cfg(feature = "database")]
pub mod pg;
#[cfg(feature = "turso")]
pub mod turso;
#[cfg(feature = "surrealdb")]
pub mod surrealdb_impl;
#[cfg(feature = "clickhouse")]
pub mod clickhouse_impl;
pub(crate) fn looks_like_framework_kind(s: &str) -> bool {
s.starts_with("auth.")
|| s.starts_with("http.")
|| s.starts_with("account.")
|| s.starts_with("config.")
}
pub(crate) fn parse_custom_kind(s: &str) -> String {
if looks_like_framework_kind(s) {
tracing::warn!(
stored_kind = %s,
"unrecognized framework audit event kind — falling back to Custom; likely version skew between emitter and reader"
);
}
s.strip_prefix("custom.").unwrap_or(s).to_string()
}
#[cfg(test)]
mod helper_tests {
use super::{looks_like_framework_kind, parse_custom_kind};
#[test]
fn framework_prefixes_detected() {
assert!(looks_like_framework_kind("auth.token.invalid"));
assert!(looks_like_framework_kind("http.request.denied"));
assert!(looks_like_framework_kind("account.created"));
assert!(looks_like_framework_kind("config.drift_detected"));
}
#[test]
fn non_framework_prefixes_ignored() {
assert!(!looks_like_framework_kind("custom.user.exported"));
assert!(!looks_like_framework_kind("user.signed_up"));
assert!(!looks_like_framework_kind("billing.invoice.paid"));
assert!(!looks_like_framework_kind(""));
}
#[test]
fn parse_custom_strips_custom_prefix() {
assert_eq!(parse_custom_kind("custom.user.exported"), "user.exported");
}
#[test]
fn parse_custom_preserves_unprefixed_user_strings() {
assert_eq!(parse_custom_kind("billing.invoice.paid"), "billing.invoice.paid");
}
#[test]
fn parse_custom_passes_through_framework_strings_for_visibility() {
assert_eq!(parse_custom_kind("auth.token.invalid"), "auth.token.invalid");
}
}
#[async_trait]
pub trait AuditStorage: Send + Sync {
async fn append(&self, event: &AuditEvent) -> Result<(), Error>;
async fn latest(&self) -> Result<Option<AuditEvent>, Error>;
async fn query_range(
&self,
from: DateTime<Utc>,
to: DateTime<Utc>,
limit: usize,
) -> Result<Vec<AuditEvent>, Error>;
async fn verify_chain(&self, from_sequence: u64) -> Result<Option<u64>, Error>;
async fn query_before(
&self,
_cutoff: DateTime<Utc>,
_limit: usize,
) -> Result<Vec<AuditEvent>, Error> {
Err(Error::Internal("query_before not implemented".into()))
}
async fn purge_before(&self, _cutoff: DateTime<Utc>) -> Result<u64, Error> {
Err(Error::Internal("purge_before not implemented".into()))
}
}