Skip to main content

rango_orm_core/
hooks.rs

1use crate::{Error, Result};
2use std::future::Future;
3use std::pin::Pin;
4
5pub type HookResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
6pub type BoxFuture<'a> = Pin<Box<dyn Future<Output = HookResult> + Send + 'a>>;
7
8/// Optional lifecycle hooks for a Model.
9/// All methods have a no-op default — implement only what you need.
10/// Returning `Err` from a `before_*` hook cancels the operation (no SQL runs); the error
11/// reaches the caller as `Error::Hook`. An `after_*` error is returned too, but the statement
12/// has already run: it is rolled back only if the caller is inside `atomic()` / a transaction.
13///
14/// Fired by `insert`, `update`, `delete`, `get_or_create` and `update_or_create`; never by
15/// bulk operations, query-builder updates/deletes or raw SQL. `#[derive(Model)]` emits an empty
16/// impl unless the struct has `#[model(hooks)]`, in which case you write `impl ModelHooks`.
17pub trait ModelHooks: Sized {
18    /// Before INSERT or UPDATE.
19    fn before_save(&mut self) -> BoxFuture<'_> {
20        Box::pin(async { Ok(()) })
21    }
22
23    /// After INSERT or UPDATE.
24    fn after_save(&self) -> BoxFuture<'_> {
25        Box::pin(async { Ok(()) })
26    }
27
28    /// Before INSERT only.
29    fn before_create(&mut self) -> BoxFuture<'_> {
30        Box::pin(async { Ok(()) })
31    }
32
33    /// After INSERT only.
34    fn after_create(&self) -> BoxFuture<'_> {
35        Box::pin(async { Ok(()) })
36    }
37
38    /// Before UPDATE only.
39    fn before_update(&mut self) -> BoxFuture<'_> {
40        Box::pin(async { Ok(()) })
41    }
42
43    /// After UPDATE only.
44    fn after_update(&self) -> BoxFuture<'_> {
45        Box::pin(async { Ok(()) })
46    }
47
48    /// Before DELETE.
49    fn before_delete(&self) -> BoxFuture<'_> {
50        Box::pin(async { Ok(()) })
51    }
52
53    /// After DELETE.
54    fn after_delete(&self) -> BoxFuture<'_> {
55        Box::pin(async { Ok(()) })
56    }
57}
58
59// ─── Ordering shared by every backend ─────────────────────────────────────────
60// Backends call these around the SQL statement; a hook error becomes `Error::Hook`.
61
62fn run(r: HookResult) -> Result<()> {
63    r.map_err(Error::Hook)
64}
65
66#[doc(hidden)]
67pub async fn before_insert<M: ModelHooks>(m: &mut M) -> Result<()> {
68    run(m.before_save().await)?;
69    run(m.before_create().await)
70}
71
72#[doc(hidden)]
73pub async fn after_insert<M: ModelHooks>(m: &M) -> Result<()> {
74    run(m.after_create().await)?;
75    run(m.after_save().await)
76}
77
78#[doc(hidden)]
79pub async fn before_update<M: ModelHooks>(m: &mut M) -> Result<()> {
80    run(m.before_save().await)?;
81    run(m.before_update().await)
82}
83
84#[doc(hidden)]
85pub async fn after_update<M: ModelHooks>(m: &M) -> Result<()> {
86    run(m.after_update().await)?;
87    run(m.after_save().await)
88}
89
90#[doc(hidden)]
91pub async fn before_delete<M: ModelHooks>(m: &M) -> Result<()> {
92    run(m.before_delete().await)
93}
94
95#[doc(hidden)]
96pub async fn after_delete<M: ModelHooks>(m: &M) -> Result<()> {
97    run(m.after_delete().await)
98}