Skip to main content

openauth_core/plugin/
db.rs

1//! Database plugin hooks and migration metadata.
2
3mod errors;
4mod handler;
5mod migration;
6
7use std::fmt;
8use std::sync::Arc;
9
10pub use handler::{
11    PluginDatabaseAfterHookFuture, PluginDatabaseAfterHookHandler, PluginDatabaseBeforeHookFuture,
12    PluginDatabaseBeforeHookHandler, PluginDatabaseHookContext,
13};
14pub use migration::PluginMigration;
15
16use errors::{mismatched_after_input, mismatched_before_input};
17
18use crate::db::{Create, DbRecord, Delete, DeleteMany, Update, UpdateMany};
19use crate::error::OpenAuthError;
20
21/// Mutating database operations that can be observed by plugins.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum PluginDatabaseOperation {
24    Create,
25    Update,
26    UpdateMany,
27    Delete,
28    DeleteMany,
29}
30
31/// Query passed to a database hook before the adapter operation runs.
32#[derive(Debug, Clone, PartialEq)]
33pub enum PluginDatabaseBeforeInput {
34    Create(Create),
35    Update(Update),
36    UpdateMany(UpdateMany),
37    Delete {
38        query: Delete,
39        snapshots: Vec<DbRecord>,
40    },
41    DeleteMany {
42        query: DeleteMany,
43        snapshots: Vec<DbRecord>,
44    },
45}
46
47impl PluginDatabaseBeforeInput {
48    pub fn operation(&self) -> PluginDatabaseOperation {
49        match self {
50            Self::Create(_) => PluginDatabaseOperation::Create,
51            Self::Update(_) => PluginDatabaseOperation::Update,
52            Self::UpdateMany(_) => PluginDatabaseOperation::UpdateMany,
53            Self::Delete { .. } => PluginDatabaseOperation::Delete,
54            Self::DeleteMany { .. } => PluginDatabaseOperation::DeleteMany,
55        }
56    }
57
58    pub fn model(&self) -> &str {
59        match self {
60            Self::Create(query) => &query.model,
61            Self::Update(query) => &query.model,
62            Self::UpdateMany(query) => &query.model,
63            Self::Delete { query, .. } => &query.model,
64            Self::DeleteMany { query, .. } => &query.model,
65        }
66    }
67}
68
69/// Action returned by a before hook.
70#[derive(Debug, PartialEq)]
71pub enum PluginDatabaseBeforeAction {
72    Continue(PluginDatabaseBeforeInput),
73    Cancel(OpenAuthError),
74}
75
76/// Query and adapter result passed to a database hook after the operation runs.
77#[derive(Debug, Clone, PartialEq)]
78pub enum PluginDatabaseAfterInput {
79    Create {
80        query: Create,
81        result: DbRecord,
82    },
83    Update {
84        query: Update,
85        result: Option<DbRecord>,
86    },
87    UpdateMany {
88        query: UpdateMany,
89        result: u64,
90    },
91    Delete {
92        query: Delete,
93        snapshots: Vec<DbRecord>,
94    },
95    DeleteMany {
96        query: DeleteMany,
97        snapshots: Vec<DbRecord>,
98        result: u64,
99    },
100}
101
102impl PluginDatabaseAfterInput {
103    pub fn operation(&self) -> PluginDatabaseOperation {
104        match self {
105            Self::Create { .. } => PluginDatabaseOperation::Create,
106            Self::Update { .. } => PluginDatabaseOperation::Update,
107            Self::UpdateMany { .. } => PluginDatabaseOperation::UpdateMany,
108            Self::Delete { .. } => PluginDatabaseOperation::Delete,
109            Self::DeleteMany { .. } => PluginDatabaseOperation::DeleteMany,
110        }
111    }
112
113    pub fn model(&self) -> &str {
114        match self {
115            Self::Create { query, .. } => &query.model,
116            Self::Update { query, .. } => &query.model,
117            Self::UpdateMany { query, .. } => &query.model,
118            Self::Delete { query, .. } => &query.model,
119            Self::DeleteMany { query, .. } => &query.model,
120        }
121    }
122}
123
124/// Executable database hook registered by a plugin.
125#[derive(Clone)]
126pub struct PluginDatabaseHook {
127    pub name: String,
128    pub operation: PluginDatabaseOperation,
129    pub before: Option<PluginDatabaseBeforeHookHandler>,
130    pub after: Option<PluginDatabaseAfterHookHandler>,
131    plugin_id: Option<String>,
132}
133
134impl PluginDatabaseHook {
135    pub fn before<F>(
136        name: impl Into<String>,
137        operation: PluginDatabaseOperation,
138        handler: F,
139    ) -> Self
140    where
141        F: Fn(
142                &PluginDatabaseHookContext<'_>,
143                PluginDatabaseBeforeInput,
144            ) -> Result<PluginDatabaseBeforeAction, OpenAuthError>
145            + Send
146            + Sync
147            + 'static,
148    {
149        Self {
150            name: name.into(),
151            operation,
152            before: Some(Arc::new(move |context, input| {
153                let result = handler(&context, input);
154                Box::pin(async move { result })
155            })),
156            after: None,
157            plugin_id: None,
158        }
159    }
160
161    pub fn before_async<F>(
162        name: impl Into<String>,
163        operation: PluginDatabaseOperation,
164        handler: F,
165    ) -> Self
166    where
167        F: for<'a> Fn(
168                PluginDatabaseHookContext<'a>,
169                PluginDatabaseBeforeInput,
170            ) -> PluginDatabaseBeforeHookFuture<'a>
171            + Send
172            + Sync
173            + 'static,
174    {
175        Self {
176            name: name.into(),
177            operation,
178            before: Some(Arc::new(handler)),
179            after: None,
180            plugin_id: None,
181        }
182    }
183
184    pub fn after<F>(name: impl Into<String>, operation: PluginDatabaseOperation, handler: F) -> Self
185    where
186        F: Fn(
187                &PluginDatabaseHookContext<'_>,
188                &PluginDatabaseAfterInput,
189            ) -> Result<(), OpenAuthError>
190            + Send
191            + Sync
192            + 'static,
193    {
194        Self {
195            name: name.into(),
196            operation,
197            before: None,
198            after: Some(Arc::new(move |context, input| {
199                let result = handler(&context, &input);
200                Box::pin(async move { result })
201            })),
202            plugin_id: None,
203        }
204    }
205
206    pub fn after_async<F>(
207        name: impl Into<String>,
208        operation: PluginDatabaseOperation,
209        handler: F,
210    ) -> Self
211    where
212        F: for<'a> Fn(
213                PluginDatabaseHookContext<'a>,
214                PluginDatabaseAfterInput,
215            ) -> PluginDatabaseAfterHookFuture<'a>
216            + Send
217            + Sync
218            + 'static,
219    {
220        Self {
221            name: name.into(),
222            operation,
223            before: None,
224            after: Some(Arc::new(handler)),
225            plugin_id: None,
226        }
227    }
228
229    pub fn before_create<F>(name: impl Into<String>, handler: F) -> Self
230    where
231        F: Fn(
232                &PluginDatabaseHookContext<'_>,
233                Create,
234            ) -> Result<PluginDatabaseBeforeAction, OpenAuthError>
235            + Send
236            + Sync
237            + 'static,
238    {
239        Self::before(
240            name,
241            PluginDatabaseOperation::Create,
242            move |context, input| match input {
243                PluginDatabaseBeforeInput::Create(query) => handler(context, query),
244                other => mismatched_before_input(PluginDatabaseOperation::Create, other),
245            },
246        )
247    }
248
249    pub fn before_create_async<F>(name: impl Into<String>, handler: F) -> Self
250    where
251        F: for<'a> Fn(PluginDatabaseHookContext<'a>, Create) -> PluginDatabaseBeforeHookFuture<'a>
252            + Send
253            + Sync
254            + 'static,
255    {
256        Self::before_async(
257            name,
258            PluginDatabaseOperation::Create,
259            move |context, input| match input {
260                PluginDatabaseBeforeInput::Create(query) => handler(context, query),
261                other => Box::pin(async move {
262                    mismatched_before_input(PluginDatabaseOperation::Create, other)
263                }),
264            },
265        )
266    }
267
268    pub fn before_update<F>(name: impl Into<String>, handler: F) -> Self
269    where
270        F: Fn(
271                &PluginDatabaseHookContext<'_>,
272                Update,
273            ) -> Result<PluginDatabaseBeforeAction, OpenAuthError>
274            + Send
275            + Sync
276            + 'static,
277    {
278        Self::before(
279            name,
280            PluginDatabaseOperation::Update,
281            move |context, input| match input {
282                PluginDatabaseBeforeInput::Update(query) => handler(context, query),
283                other => mismatched_before_input(PluginDatabaseOperation::Update, other),
284            },
285        )
286    }
287
288    pub fn before_update_many<F>(name: impl Into<String>, handler: F) -> Self
289    where
290        F: Fn(
291                &PluginDatabaseHookContext<'_>,
292                UpdateMany,
293            ) -> Result<PluginDatabaseBeforeAction, OpenAuthError>
294            + Send
295            + Sync
296            + 'static,
297    {
298        Self::before(
299            name,
300            PluginDatabaseOperation::UpdateMany,
301            move |context, input| match input {
302                PluginDatabaseBeforeInput::UpdateMany(query) => handler(context, query),
303                other => mismatched_before_input(PluginDatabaseOperation::UpdateMany, other),
304            },
305        )
306    }
307
308    pub fn before_delete<F>(name: impl Into<String>, handler: F) -> Self
309    where
310        F: Fn(
311                &PluginDatabaseHookContext<'_>,
312                Delete,
313                Vec<DbRecord>,
314            ) -> Result<PluginDatabaseBeforeAction, OpenAuthError>
315            + Send
316            + Sync
317            + 'static,
318    {
319        Self::before(
320            name,
321            PluginDatabaseOperation::Delete,
322            move |context, input| match input {
323                PluginDatabaseBeforeInput::Delete { query, snapshots } => {
324                    handler(context, query, snapshots)
325                }
326                other => mismatched_before_input(PluginDatabaseOperation::Delete, other),
327            },
328        )
329    }
330
331    pub fn before_delete_many<F>(name: impl Into<String>, handler: F) -> Self
332    where
333        F: Fn(
334                &PluginDatabaseHookContext<'_>,
335                DeleteMany,
336                Vec<DbRecord>,
337            ) -> Result<PluginDatabaseBeforeAction, OpenAuthError>
338            + Send
339            + Sync
340            + 'static,
341    {
342        Self::before(
343            name,
344            PluginDatabaseOperation::DeleteMany,
345            move |context, input| match input {
346                PluginDatabaseBeforeInput::DeleteMany { query, snapshots } => {
347                    handler(context, query, snapshots)
348                }
349                other => mismatched_before_input(PluginDatabaseOperation::DeleteMany, other),
350            },
351        )
352    }
353
354    pub fn after_create<F>(name: impl Into<String>, handler: F) -> Self
355    where
356        F: Fn(&PluginDatabaseHookContext<'_>, &Create, &DbRecord) -> Result<(), OpenAuthError>
357            + Send
358            + Sync
359            + 'static,
360    {
361        Self::after(
362            name,
363            PluginDatabaseOperation::Create,
364            move |context, input| match input {
365                PluginDatabaseAfterInput::Create { query, result } => {
366                    handler(context, query, result)
367                }
368                other => mismatched_after_input(PluginDatabaseOperation::Create, other),
369            },
370        )
371    }
372
373    pub fn after_update<F>(name: impl Into<String>, handler: F) -> Self
374    where
375        F: Fn(
376                &PluginDatabaseHookContext<'_>,
377                &Update,
378                &Option<DbRecord>,
379            ) -> Result<(), OpenAuthError>
380            + Send
381            + Sync
382            + 'static,
383    {
384        Self::after(
385            name,
386            PluginDatabaseOperation::Update,
387            move |context, input| match input {
388                PluginDatabaseAfterInput::Update { query, result } => {
389                    handler(context, query, result)
390                }
391                other => mismatched_after_input(PluginDatabaseOperation::Update, other),
392            },
393        )
394    }
395
396    pub fn after_update_many<F>(name: impl Into<String>, handler: F) -> Self
397    where
398        F: Fn(&PluginDatabaseHookContext<'_>, &UpdateMany, u64) -> Result<(), OpenAuthError>
399            + Send
400            + Sync
401            + 'static,
402    {
403        Self::after(
404            name,
405            PluginDatabaseOperation::UpdateMany,
406            move |context, input| match input {
407                PluginDatabaseAfterInput::UpdateMany { query, result } => {
408                    handler(context, query, *result)
409                }
410                other => mismatched_after_input(PluginDatabaseOperation::UpdateMany, other),
411            },
412        )
413    }
414
415    pub fn after_delete<F>(name: impl Into<String>, handler: F) -> Self
416    where
417        F: Fn(&PluginDatabaseHookContext<'_>, &Delete, &[DbRecord]) -> Result<(), OpenAuthError>
418            + Send
419            + Sync
420            + 'static,
421    {
422        Self::after(
423            name,
424            PluginDatabaseOperation::Delete,
425            move |context, input| match input {
426                PluginDatabaseAfterInput::Delete { query, snapshots } => {
427                    handler(context, query, snapshots)
428                }
429                other => mismatched_after_input(PluginDatabaseOperation::Delete, other),
430            },
431        )
432    }
433
434    pub fn after_delete_many<F>(name: impl Into<String>, handler: F) -> Self
435    where
436        F: Fn(
437                &PluginDatabaseHookContext<'_>,
438                &DeleteMany,
439                &[DbRecord],
440                u64,
441            ) -> Result<(), OpenAuthError>
442            + Send
443            + Sync
444            + 'static,
445    {
446        Self::after(
447            name,
448            PluginDatabaseOperation::DeleteMany,
449            move |context, input| match input {
450                PluginDatabaseAfterInput::DeleteMany {
451                    query,
452                    snapshots,
453                    result,
454                } => handler(context, query, snapshots, *result),
455                other => mismatched_after_input(PluginDatabaseOperation::DeleteMany, other),
456            },
457        )
458    }
459
460    pub fn plugin_id(&self) -> Option<&str> {
461        self.plugin_id.as_deref()
462    }
463
464    pub fn with_plugin_id(mut self, plugin_id: impl Into<String>) -> Self {
465        self.plugin_id = Some(plugin_id.into());
466        self
467    }
468
469    pub fn has_overlapping_phase(&self, other: &Self) -> bool {
470        self.name == other.name
471            && self.operation == other.operation
472            && ((self.before.is_some() && other.before.is_some())
473                || (self.after.is_some() && other.after.is_some()))
474    }
475}
476
477impl fmt::Debug for PluginDatabaseHook {
478    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
479        formatter
480            .debug_struct("PluginDatabaseHook")
481            .field("name", &self.name)
482            .field("operation", &self.operation)
483            .field("before", &self.before.as_ref().map(|_| "<before>"))
484            .field("after", &self.after.as_ref().map(|_| "<after>"))
485            .field("plugin_id", &self.plugin_id)
486            .finish()
487    }
488}