use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuditStatus {
Success,
Failure,
Denied,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuditSeverity {
Info,
Warning,
Error,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
pub id: String,
pub timestamp: DateTime<Utc>,
pub event_type: String,
pub user_id: Option<String>,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub resource_type: Option<String>,
pub resource_id: Option<String>,
pub action: String,
pub status: AuditStatus,
pub severity: AuditSeverity,
pub method: Option<String>,
pub path: Option<String>,
pub status_code: Option<u16>,
pub metadata: HashMap<String, serde_json::Value>,
pub error: Option<String>,
pub request_body: Option<String>,
pub response_body: Option<String>,
pub duration_ms: Option<u64>,
}
impl AuditEvent {
pub fn new(event_type: impl Into<String>) -> Self {
Self {
id: Uuid::new_v4().to_string(),
timestamp: Utc::now(),
event_type: event_type.into(),
user_id: None,
ip_address: None,
user_agent: None,
resource_type: None,
resource_id: None,
action: "unknown".to_string(),
status: AuditStatus::Success,
severity: AuditSeverity::Info,
method: None,
path: None,
status_code: None,
metadata: HashMap::new(),
error: None,
request_body: None,
response_body: None,
duration_ms: None,
}
}
pub fn user(mut self, user_id: impl Into<String>) -> Self {
self.user_id = Some(user_id.into());
self
}
pub fn ip(mut self, ip: impl Into<String>) -> Self {
self.ip_address = Some(ip.into());
self
}
pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
self.user_agent = Some(ua.into());
self
}
pub fn resource(mut self, resource_type: impl Into<String>) -> Self {
self.resource_type = Some(resource_type.into());
self
}
pub fn resource_id(mut self, id: impl Into<String>) -> Self {
self.resource_id = Some(id.into());
self
}
pub fn action(mut self, action: impl Into<String>) -> Self {
self.action = action.into();
self
}
pub fn status(mut self, status: AuditStatus) -> Self {
self.status = status;
self
}
pub fn severity(mut self, severity: AuditSeverity) -> Self {
self.severity = severity;
self
}
pub fn method(mut self, method: impl Into<String>) -> Self {
self.method = Some(method.into());
self
}
pub fn path(mut self, path: impl Into<String>) -> Self {
self.path = Some(path.into());
self
}
pub fn status_code(mut self, code: u16) -> Self {
self.status_code = Some(code);
self
}
pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
pub fn error(mut self, error: impl Into<String>) -> Self {
self.error = Some(error.into());
self
}
pub fn request_body(mut self, body: impl Into<String>) -> Self {
self.request_body = Some(body.into());
self
}
pub fn response_body(mut self, body: impl Into<String>) -> Self {
self.response_body = Some(body.into());
self
}
pub fn duration_ms(mut self, duration: u64) -> Self {
self.duration_ms = Some(duration);
self
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audit_event_creation() {
let event = AuditEvent::new("test.event")
.user("alice")
.action("test")
.status(AuditStatus::Success);
assert_eq!(event.event_type, "test.event");
assert_eq!(event.user_id, Some("alice".to_string()));
assert_eq!(event.action, "test");
assert_eq!(event.status, AuditStatus::Success);
}
#[test]
fn test_audit_event_to_json() {
let event = AuditEvent::new("test.event");
let json = event.to_json();
assert!(json.is_ok());
}
#[test]
fn test_audit_severity_ordering() {
assert!(AuditSeverity::Info < AuditSeverity::Warning);
assert!(AuditSeverity::Warning < AuditSeverity::Error);
assert!(AuditSeverity::Error < AuditSeverity::Critical);
}
}