Skip to main content

backbone_core/
write_guard.rs

1//! The write guard: the rules a write must satisfy, asked on every path the
2//! generic service writes through.
3//!
4//! [`GenericCrudService`](crate::service::GenericCrudService) consults its
5//! guard on create, update (full and partial, single and bulk), soft delete,
6//! restore and hard delete, after the write's resulting row is known and
7//! before anything is persisted. A guard answers with violations to refuse and
8//! violations to only report: a rule that runs in shadow is logged and the
9//! write proceeds, so a newly compiled rule shows what it would refuse before
10//! it refuses anything. The schema generator compiles a module's declared rules
11//! into guards; without one, every write is allowed ([`AllowAll`]).
12
13use async_trait::async_trait;
14
15use crate::violation::Violation;
16
17/// What kind of write is being checked.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum WriteKind {
20    Create,
21    /// A full or partial update.
22    Update,
23    /// A soft delete.
24    Delete,
25    Restore,
26    /// Removal of a soft-deleted row for good.
27    HardDelete,
28}
29
30/// One write, as the guard sees it.
31#[derive(Debug)]
32pub struct WriteCtx<'a, E> {
33    pub kind: WriteKind,
34    /// The row as stored before the write; `None` on create.
35    pub before: Option<&'a E>,
36    /// The row the write would store; `None` on delete and hard delete.
37    pub after: Option<&'a E>,
38}
39
40/// A guard's answer.
41#[derive(Debug, Default, Clone, PartialEq)]
42pub struct GuardOutcome {
43    /// Broken rules that refuse the write.
44    pub refuse: Vec<Violation>,
45    /// Broken rules running in shadow: reported, not refused.
46    pub shadow: Vec<Violation>,
47}
48
49impl GuardOutcome {
50    pub fn allow() -> Self {
51        Self::default()
52    }
53
54    /// Fold another guard's answer into this one.
55    pub fn merge(mut self, other: GuardOutcome) -> Self {
56        self.refuse.extend(other.refuse);
57        self.shadow.extend(other.shadow);
58        self
59    }
60}
61
62/// The rules one entity's writes must satisfy.
63#[async_trait]
64pub trait WriteGuard<E>: Send + Sync {
65    async fn check(&self, ctx: &WriteCtx<'_, E>) -> GuardOutcome;
66}
67
68/// A guard that refuses nothing: the default for an entity whose schema
69/// declares no rules.
70#[derive(Debug, Default, Clone, Copy)]
71pub struct AllowAll;
72
73#[async_trait]
74impl<E: Sync> WriteGuard<E> for AllowAll {
75    async fn check(&self, _ctx: &WriteCtx<'_, E>) -> GuardOutcome {
76        GuardOutcome::allow()
77    }
78}