zino_core/model/
hook.rs

1use super::QueryContext;
2use crate::{
3    Map,
4    error::Error,
5    model::{Model, Mutation, Query},
6};
7use std::borrow::Cow;
8
9/// Hooks for the model.
10///
11/// This trait can be derived by `zino_derive::ModelHooks`.
12pub trait ModelHooks: Model {
13    /// Model data.
14    type Data: Default;
15    /// Extension data.
16    type Extension: Clone + Send + Sync + 'static;
17
18    /// A hook running before extracting the model data.
19    #[inline]
20    async fn before_extract() -> Result<(), Error> {
21        Ok(())
22    }
23
24    /// A hook running after extracting the model data.
25    #[inline]
26    async fn after_extract(&mut self, _extension: Self::Extension) -> Result<(), Error> {
27        Ok(())
28    }
29
30    /// A hook running before validating the model data.
31    #[inline]
32    async fn before_validation(
33        _model: &mut Map,
34        _extension: Option<&Self::Extension>,
35    ) -> Result<(), Error> {
36        Ok(())
37    }
38
39    /// A hook running after validating the model data.
40    #[inline]
41    async fn after_validation(&mut self, _model: &mut Map) -> Result<(), Error> {
42        Ok(())
43    }
44
45    /// A hook running before creating the table.
46    #[inline]
47    async fn before_create_table() -> Result<(), Error> {
48        Ok(())
49    }
50
51    /// A hook running after creating the table.
52    #[inline]
53    async fn after_create_table() -> Result<(), Error> {
54        Ok(())
55    }
56
57    /// A hook running before preparing a query for the model.
58    /// It can be used to calculate a dynamic table name.
59    #[inline]
60    async fn before_prepare(&self) -> Result<Option<String>, Error> {
61        Ok(None)
62    }
63
64    /// A hook running before scanning the table.
65    #[inline]
66    async fn before_scan(query: &str) -> Result<QueryContext, Error> {
67        let model_name = Self::model_name();
68        let ctx = QueryContext::new(model_name);
69        let query_id = ctx.query_id().to_string();
70        tracing::debug!(model_name, query_id, query);
71        Ok(ctx)
72    }
73
74    /// A hook running after scanning the table.
75    async fn after_scan(ctx: &QueryContext) -> Result<(), Error> {
76        let model_name = ctx.model_name();
77        let query_id = ctx.query_id().to_string();
78        let query = ctx.query();
79        let arguments = ctx.format_arguments();
80        let message = match ctx.rows_affected() {
81            Some(0) => Cow::Borrowed("no rows affected or fetched"),
82            Some(1) => Cow::Borrowed("only one row affected or fetched"),
83            Some(num_rows) if num_rows > 1 => {
84                Cow::Owned(format!("{num_rows} rows affected or fetched"))
85            }
86            _ => Cow::Borrowed("query result has not been recorded"),
87        };
88        let execution_time = ctx.start_time().elapsed();
89        let execution_time_millis = execution_time.as_millis();
90        tracing::info!(
91            model_name,
92            query_id,
93            query,
94            arguments,
95            execution_time_millis,
96            "{message}"
97        );
98        Ok(())
99    }
100
101    /// A hook running before checking the constraints when inserting a model into the table.
102    #[inline]
103    async fn before_insert_check(
104        &mut self,
105        _extension: Option<&Self::Extension>,
106    ) -> Result<(), Error> {
107        Ok(())
108    }
109
110    /// A hook running before inserting a model into the table.
111    #[inline]
112    async fn before_insert(&mut self) -> Result<Self::Data, Error> {
113        self.before_save().await
114    }
115
116    /// A hook running after inserting a model into the table.
117    #[inline]
118    async fn after_insert(ctx: &QueryContext, data: Self::Data) -> Result<(), Error> {
119        Self::after_save(ctx, data).await?;
120        #[cfg(feature = "metrics")]
121        ctx.emit_metrics("insert");
122        Ok(())
123    }
124
125    /// A hook running before logically deleting a model from the table.
126    #[inline]
127    async fn before_soft_delete(&mut self) -> Result<Self::Data, Error> {
128        self.before_save().await
129    }
130
131    /// A hook running after logically deleting a model from the table.
132    #[inline]
133    async fn after_soft_delete(ctx: &QueryContext, data: Self::Data) -> Result<(), Error> {
134        Self::after_save(ctx, data).await?;
135        #[cfg(feature = "metrics")]
136        ctx.emit_metrics("soft_delete");
137        Ok(())
138    }
139
140    /// A hook running before locking a model in the table.
141    #[inline]
142    async fn before_lock(&mut self) -> Result<Self::Data, Error> {
143        self.before_save().await
144    }
145
146    /// A hook running after locking a model in the table.
147    #[inline]
148    async fn after_lock(ctx: &QueryContext, data: Self::Data) -> Result<(), Error> {
149        Self::after_save(ctx, data).await?;
150        #[cfg(feature = "metrics")]
151        ctx.emit_metrics("lock");
152        Ok(())
153    }
154
155    /// A hook running before archiving a model in the table.
156    #[inline]
157    async fn before_archive(&mut self) -> Result<Self::Data, Error> {
158        self.before_save().await
159    }
160
161    /// A hook running after archiving a model in the table.
162    #[inline]
163    async fn after_archive(ctx: &QueryContext, data: Self::Data) -> Result<(), Error> {
164        Self::after_save(ctx, data).await?;
165        #[cfg(feature = "metrics")]
166        ctx.emit_metrics("archive");
167        Ok(())
168    }
169
170    /// A hook running before updating a model in the table.
171    #[inline]
172    async fn before_update(&mut self) -> Result<Self::Data, Error> {
173        self.before_save().await
174    }
175
176    /// A hook running after updating a model in the table.
177    #[inline]
178    async fn after_update(ctx: &QueryContext, data: Self::Data) -> Result<(), Error> {
179        Self::after_save(ctx, data).await?;
180        #[cfg(feature = "metrics")]
181        ctx.emit_metrics("update");
182        Ok(())
183    }
184
185    /// A hook running before updating or inserting a model into the table.
186    #[inline]
187    async fn before_upsert(&mut self) -> Result<Self::Data, Error> {
188        self.before_save().await
189    }
190
191    /// A hook running after updating or inserting a model into the table.
192    #[inline]
193    async fn after_upsert(ctx: &QueryContext, data: Self::Data) -> Result<(), Error> {
194        Self::after_save(ctx, data).await?;
195        #[cfg(feature = "metrics")]
196        ctx.emit_metrics("upsert");
197        Ok(())
198    }
199
200    /// A hook running before saving a model into the table.
201    #[inline]
202    async fn before_save(&mut self) -> Result<Self::Data, Error> {
203        Ok(Self::Data::default())
204    }
205
206    /// A hook running after saving a model into the table.
207    #[inline]
208    async fn after_save(ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
209        if !ctx.is_success() {
210            ctx.record_error("fail to save a model into the table");
211        }
212        Ok(())
213    }
214
215    /// A hook running before deleting a model from the table.
216    #[inline]
217    async fn before_delete(&mut self) -> Result<Self::Data, Error> {
218        Ok(Self::Data::default())
219    }
220
221    /// A hook running after deleting a model from the table.
222    #[inline]
223    async fn after_delete(self, ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
224        let query = ctx.query();
225        let query_id = ctx.query_id().to_string();
226        if ctx.is_success() {
227            tracing::warn!(query, query_id, "a model was deleted from the table");
228        } else {
229            tracing::error!(query, query_id, "fail to detele a model from the table");
230        }
231        #[cfg(feature = "metrics")]
232        ctx.emit_metrics("delete");
233        Ok(())
234    }
235
236    /// A hook running before counting the models in the table.
237    #[inline]
238    async fn before_count(_query: &Query) -> Result<(), Error> {
239        Ok(())
240    }
241
242    /// A hook running after counting the models in the table.
243    #[inline]
244    async fn after_count(ctx: &QueryContext) -> Result<(), Error> {
245        if !ctx.is_success() {
246            ctx.record_error("fail to count the models in the table");
247        }
248        #[cfg(feature = "metrics")]
249        ctx.emit_metrics("count");
250        Ok(())
251    }
252
253    /// A hook running before aggregating the models in the table.
254    #[inline]
255    async fn before_aggregate(_query: &Query) -> Result<(), Error> {
256        Ok(())
257    }
258
259    /// A hook running after aggregating the models in the table.
260    #[inline]
261    async fn after_aggregate(ctx: &QueryContext) -> Result<(), Error> {
262        if !ctx.is_success() {
263            ctx.record_error("fail to aggregate the models in the table");
264        }
265        #[cfg(feature = "metrics")]
266        ctx.emit_metrics("aggregate");
267        Ok(())
268    }
269
270    /// A hook running before selecting the models with a `Query` from the table.
271    #[inline]
272    async fn before_query(_query: &Query) -> Result<(), Error> {
273        Ok(())
274    }
275
276    /// A hook running after selecting the models with a `Query` from the table.
277    #[inline]
278    async fn after_query(ctx: &QueryContext) -> Result<(), Error> {
279        if !ctx.is_success() {
280            ctx.record_error("fail to select the models from the table");
281        }
282        #[cfg(feature = "metrics")]
283        ctx.emit_metrics("query");
284        Ok(())
285    }
286
287    /// A hook running before updating the models with a `Mutation` in the table.
288    #[inline]
289    async fn before_mutation(_query: &Query, _mutation: &mut Mutation) -> Result<(), Error> {
290        Ok(())
291    }
292
293    /// A hook running after updating the models with a `Mutation` in the table.
294    #[inline]
295    async fn after_mutation(ctx: &QueryContext) -> Result<(), Error> {
296        if !ctx.is_success() {
297            ctx.record_error("fail to update the models in the table");
298        }
299        #[cfg(feature = "metrics")]
300        ctx.emit_metrics("mutation");
301        Ok(())
302    }
303
304    /// A hook running before listing the models with a `Query` from the table.
305    #[inline]
306    async fn before_list(
307        _query: &mut Query,
308        _extension: Option<&Self::Extension>,
309    ) -> Result<(), Error> {
310        Ok(())
311    }
312
313    /// A hook running before batch deleting the models with a `Query` from the table.
314    #[inline]
315    async fn before_batch_delete(
316        _query: &mut Query,
317        _extension: Option<&Self::Extension>,
318    ) -> Result<(), Error> {
319        Ok(())
320    }
321
322    /// A hook running after a query population for the model.
323    #[inline]
324    async fn after_populate(_model: &mut Map) -> Result<(), Error> {
325        Ok(())
326    }
327
328    /// A hook running after decoding the model as a `Map`.
329    #[inline]
330    async fn after_decode(_model: &mut Map) -> Result<(), Error> {
331        Ok(())
332    }
333
334    /// A hook running before returning the model data as a HTTP response.
335    #[inline]
336    async fn before_respond(
337        _model: &mut Map,
338        _extension: Option<&Self::Extension>,
339    ) -> Result<(), Error> {
340        Ok(())
341    }
342
343    /// A hook running before mocking the model data.
344    #[inline]
345    async fn before_mock() -> Result<Map, Error> {
346        Ok(Map::new())
347    }
348
349    /// A hook running after mocking the model data.
350    #[inline]
351    async fn after_mock(&mut self) -> Result<(), Error> {
352        Ok(())
353    }
354}