auditlog 0.1.0

Audit trail for your data models — an ORM-agnostic core with a pluggable, async sqlx backend (SQLite & Postgres).
Documentation
//! `auditlog` — an audit trail for your data models.
//!
//! `auditlog` records every create / update / destroy of your models into a single polymorphic
//! `audits` table, capturing *what* changed (a diff), *who* changed it, *when*, from *where*, and an
//! optional comment — and lets you query that history and reconstruct past revisions.
//!
//! `auditlog` is **ORM-agnostic**: you implement the small [`Auditable`] trait for your model and
//! hand it a [`Backend`]. A ready-to-use async [`SqlxBackend`] (SQLite & Postgres) is included,
//! plus an in-memory [`MemoryBackend`] for tests.
//!
//! # Quick start
//!
//! ```
//! use auditlog::{Auditable, AuditOptions, AuditId, ValueMap, MemoryBackend};
//! use serde_json::json;
//!
//! struct Post { id: i64, title: String, body: String }
//!
//! impl Auditable for Post {
//!     fn auditable_type() -> &'static str { "Post" }
//!     fn auditable_id(&self) -> AuditId { self.id.into() }
//!     fn audited_attributes(&self) -> ValueMap {
//!         let mut m = ValueMap::new();
//!         m.insert("id".into(), json!(self.id));
//!         m.insert("title".into(), json!(self.title));
//!         m.insert("body".into(), json!(self.body));
//!         m
//!     }
//!     fn audit_options() -> AuditOptions { AuditOptions::default() }
//! }
//!
//! # async fn run() -> auditlog::Result<()> {
//! let backend = MemoryBackend::new();
//!
//! // create
//! let mut post = Post { id: 1, title: "Hello".into(), body: "...".into() };
//! post.audited_create(&backend).await?;
//!
//! // update (diff old → new)
//! let old = Post { id: 1, title: "Hello".into(), body: "...".into() };
//! post.title = "Hello, world".into();
//! post.audited_update(&backend, &old).await?;
//!
//! let audits = Post::audits(&backend, 1).await?;
//! assert_eq!(audits.len(), 2);
//! # Ok(()) }
//! # tokio::runtime::Builder::new_current_thread().build().unwrap().block_on(run()).unwrap();
//! ```
//!
//! # Who made the change
//!
//! Wrap your unit of work in an [`as_user`] scope: every audit recorded inside the scope is
//! attributed to the given actor. The acting user is read from a [`tokio::task_local`] context, so
//! it is isolated per task:
//!
//! ```no_run
//! # use auditlog::*;
//! # async fn run(backend: &dyn Backend, post: &(impl Auditable + Sized)) -> Result<()> {
//! as_user(Actor::record("User", 7), async {
//!     post.audited_create(backend).await
//! }).await?;
//! # Ok(()) }
//! ```
//!
//! # Feature flags
//!
//! * `sqlite` *(default)* — the [`SqlxBackend`] on SQLite.
//! * `postgres` — the [`SqlxBackend`] on Postgres.
//!
//! See the crate's `docs/SPEC.md` for the exhaustive, code-accurate behavior specification.

#![forbid(unsafe_code)]
#![warn(missing_docs)]

mod action;
mod actor;
mod audit;
mod auditable;
mod backend;
mod changes;
mod config;
mod context;
mod engine;
mod error;
mod id;
mod query;
mod revision;

pub mod matchers;

pub use action::{Action, ParseActionError};
pub use actor::Actor;
pub use audit::{Audit, NewAudit, UndoPlan};
pub use auditable::Auditable;
pub use backend::{AuditQuery, Backend, MemoryBackend, Order};
pub use changes::{AuditedChanges, ChangeValue, FILTERED, REDACTED, ValueMap};
pub use config::{
    AuditOptions, AuditOptionsBuilder, DEFAULT_IGNORED_ATTRIBUTES, GlobalConfig, auditing_enabled,
    config, global_config, global_max_audits, set_auditing_enabled, set_type_enabled, type_enabled,
};
pub use context::{
    AuditContext, as_user, as_user_sync, current_context, with_auditing, with_auditing_sync,
    with_context, with_context_sync, without_auditing, without_auditing_sync,
};
pub use error::{AuditError, Result};
pub use id::AuditId;
pub use query::AuditQueryBuilder;
pub use revision::Revision;

#[cfg(any(feature = "sqlite", feature = "postgres"))]
pub use backend::SqlxBackend;

/// Re-export so downstream code and doc examples can reference the same `async_trait`.
pub use async_trait::async_trait;