use crate::error::Result;
use sqlx::PgPool;
#[async_trait::async_trait]
pub trait Lifecycle: Sized + Send + Sync {
async fn before_create(&mut self, _pool: &PgPool) -> Result<()> {
Ok(())
}
async fn after_create(&self, _pool: &PgPool) -> Result<()> {
Ok(())
}
async fn before_update(&mut self, _pool: &PgPool) -> Result<()> {
Ok(())
}
async fn after_update(&self, _pool: &PgPool) -> Result<()> {
Ok(())
}
async fn before_delete(&self, _pool: &PgPool) -> Result<()> {
Ok(())
}
async fn after_delete(&self, _pool: &PgPool) -> Result<()> {
Ok(())
}
async fn before_save(&mut self, pool: &PgPool) -> Result<()> {
self.validate(pool).await?;
Ok(())
}
async fn after_save(&self, _pool: &PgPool) -> Result<()> {
Ok(())
}
async fn validate(&self, _pool: &PgPool) -> Result<()> {
Ok(())
}
}
pub struct HookExecutor<T> {
model: T,
}
impl<T: Lifecycle> HookExecutor<T> {
pub fn new(model: T) -> Self {
Self { model }
}
pub async fn create(mut self, pool: &PgPool) -> Result<T> {
self.model.before_create(pool).await?;
self.model.before_save(pool).await?;
self.model.after_save(pool).await?;
self.model.after_create(pool).await?;
Ok(self.model)
}
pub async fn update(mut self, pool: &PgPool) -> Result<T> {
self.model.before_update(pool).await?;
self.model.before_save(pool).await?;
self.model.after_save(pool).await?;
self.model.after_update(pool).await?;
Ok(self.model)
}
pub async fn delete(self, pool: &PgPool) -> Result<T> {
self.model.before_delete(pool).await?;
self.model.after_delete(pool).await?;
Ok(self.model)
}
pub fn into_inner(self) -> T {
self.model
}
}
impl<T: Lifecycle> AsRef<T> for HookExecutor<T> {
fn as_ref(&self) -> &T {
&self.model
}
}
impl<T: Lifecycle> AsMut<T> for HookExecutor<T> {
fn as_mut(&mut self) -> &mut T {
&mut self.model
}
}
#[async_trait::async_trait]
pub trait ModelObserver<T>: Send + Sync {
async fn on_created(&self, model: &T, pool: &PgPool) -> Result<()>;
async fn on_updated(&self, model: &T, pool: &PgPool) -> Result<()>;
async fn on_deleted(&self, model: &T, pool: &PgPool) -> Result<()>;
}
pub struct ObserverRegistry<T> {
observers: Vec<Box<dyn ModelObserver<T>>>,
}
impl<T> ObserverRegistry<T> {
pub fn new() -> Self {
Self {
observers: Vec::new(),
}
}
pub fn register(&mut self, observer: Box<dyn ModelObserver<T>>) {
self.observers.push(observer);
}
pub async fn notify_created(&self, model: &T, pool: &PgPool) -> Result<()> {
for observer in &self.observers {
observer.on_created(model, pool).await?;
}
Ok(())
}
pub async fn notify_updated(&self, model: &T, pool: &PgPool) -> Result<()> {
for observer in &self.observers {
observer.on_updated(model, pool).await?;
}
Ok(())
}
pub async fn notify_deleted(&self, model: &T, pool: &PgPool) -> Result<()> {
for observer in &self.observers {
observer.on_deleted(model, pool).await?;
}
Ok(())
}
}
impl<T> Default for ObserverRegistry<T> {
fn default() -> Self {
Self::new()
}
}
pub struct ValidationContext {
errors: Vec<String>,
}
impl ValidationContext {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn add_error(&mut self, field: impl Into<String>, message: impl Into<String>) {
self.errors
.push(format!("{}: {}", field.into(), message.into()));
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn into_result(self) -> Result<()> {
if self.has_errors() {
Err(crate::error::CoreError::Validation(self.errors.join(", ")))
} else {
Ok(())
}
}
}
impl Default for ValidationContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct TestModel {
name: String,
validated: bool,
}
#[async_trait::async_trait]
impl Lifecycle for TestModel {
async fn validate(&self, _pool: &PgPool) -> Result<()> {
let mut ctx = ValidationContext::new();
if self.name.is_empty() {
ctx.add_error("name", "cannot be empty");
}
ctx.into_result()
}
async fn before_create(&mut self, _pool: &PgPool) -> Result<()> {
self.validated = true;
Ok(())
}
}
#[test]
fn test_validation_context() {
let mut ctx = ValidationContext::new();
assert!(!ctx.has_errors());
ctx.add_error("field1", "error1");
assert!(ctx.has_errors());
assert_eq!(ctx.errors().len(), 1);
ctx.add_error("field2", "error2");
assert_eq!(ctx.errors().len(), 2);
}
#[test]
fn test_validation_context_result() {
let ctx = ValidationContext::new();
assert!(ctx.into_result().is_ok());
let mut ctx = ValidationContext::new();
ctx.add_error("field", "error");
assert!(ctx.into_result().is_err());
}
#[test]
fn test_hook_executor_creation() {
let model = TestModel {
name: "test".to_string(),
validated: false,
};
let executor = HookExecutor::new(model);
assert_eq!(executor.as_ref().name, "test");
assert!(!executor.as_ref().validated);
}
}