auditlog 0.1.0

Audit trail for your data models — an ORM-agnostic core with a pluggable, async sqlx backend (SQLite & Postgres).
Documentation
//! The user/actor attributed to an audit.

use crate::id::AuditId;

/// Who performed an audited change.
///
/// An actor is either a polymorphic record `user` or a plain-string `username`, which are mutually
/// exclusive:
///
/// * [`Actor::Record`] populates `user_id` + `user_type` and leaves `username` empty.
/// * [`Actor::Name`] populates `username` and leaves `user_id` + `user_type` empty.
///
/// `From<&str>` / `From<String>` build a [`Actor::Name`]; use [`Actor::record`] for a model user.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Actor {
    /// A model/record user, e.g. `("User", 7)`.
    Record {
        /// The user's polymorphic type name, e.g. `"User"`.
        user_type: String,
        /// The user's id.
        user_id: AuditId,
    },
    /// A plain username string.
    Name(String),
}

impl Actor {
    /// Build a record actor from a type name and id.
    pub fn record(user_type: impl Into<String>, user_id: impl Into<AuditId>) -> Self {
        Actor::Record {
            user_type: user_type.into(),
            user_id: user_id.into(),
        }
    }

    /// Build a username actor.
    pub fn name(username: impl Into<String>) -> Self {
        Actor::Name(username.into())
    }

    /// Split into the three mutually-exclusive columns `(user_id, user_type, username)`.
    pub(crate) fn into_columns(self) -> (Option<AuditId>, Option<String>, Option<String>) {
        match self {
            Actor::Record { user_type, user_id } => (Some(user_id), Some(user_type), None),
            Actor::Name(name) => (None, None, Some(name)),
        }
    }

    /// Rebuild an actor from stored columns: the record user if its columns are present, otherwise
    /// the username string.
    pub(crate) fn from_columns(
        user_id: Option<AuditId>,
        user_type: Option<String>,
        username: Option<String>,
    ) -> Option<Actor> {
        match (user_id, user_type) {
            (Some(id), Some(ty)) => Some(Actor::Record {
                user_type: ty,
                user_id: id,
            }),
            _ => username.map(Actor::Name),
        }
    }
}

impl From<&str> for Actor {
    fn from(value: &str) -> Self {
        Actor::Name(value.to_owned())
    }
}

impl From<String> for Actor {
    fn from(value: String) -> Self {
        Actor::Name(value)
    }
}

impl From<&String> for Actor {
    fn from(value: &String) -> Self {
        Actor::Name(value.clone())
    }
}