crdb_core/
object.rs

1use crate::{BinPtr, Db, DbPtr, Importance, ObjectId, ResultExt, TypeId, User};
2#[cfg(doc)]
3use std::collections::{BTreeMap, HashMap};
4use std::{any::Any, collections::HashSet, sync::Arc};
5
6pub trait CanDoCallbacks: waaaa::Send + waaaa::Sync {
7    fn get<T: Object>(
8        &self,
9        ptr: DbPtr<T>,
10    ) -> impl '_ + waaaa::Future<Output = crate::Result<Arc<T>>>;
11}
12
13impl<D: Db> CanDoCallbacks for D {
14    async fn get<T: Object>(&self, object_id: DbPtr<T>) -> crate::Result<Arc<T>> {
15        self.get_latest::<T>(ObjectId(object_id.id), Importance::NONE)
16            .await
17            .wrap_with_context(|| format!("requesting {object_id:?} from database"))
18    }
19}
20
21pub trait Event:
22    Any + Eq + Send + Sync + deepsize::DeepSizeOf + for<'a> serde::Deserialize<'a> + serde::Serialize
23{
24    fn required_binaries(&self) -> Vec<BinPtr>;
25}
26
27/// Note that for this trait to be implemented correctly, the `Eq` trait should be
28/// equivalent to equality of the JSON-serialized representation. In particular, this
29/// means that things like [`HashMap`]s should be banned, and [`BTreeMap`]s should be
30/// preferred.
31///
32/// Note that due to postgresql limitations reasons, this type MUST NOT include any
33/// null byte in the serialized JSON. Including them will result in internal server
34/// errors.
35pub trait Object:
36    Any
37    + Clone
38    + Eq
39    + Send
40    + Sync
41    + deepsize::DeepSizeOf
42    + for<'a> serde::Deserialize<'a>
43    + serde::Serialize
44{
45    /// Note that due to postgresql limitations reasons, this type MUST NOT include any
46    /// null byte in the serialized JSON. Trying to submit one such event will result
47    /// in the event being rejected by the server.
48    type Event: Event;
49
50    fn type_ulid() -> &'static TypeId;
51    fn snapshot_version() -> i32 {
52        0
53    }
54    /// Parse this object type from an older snapshot version
55    ///
56    /// Note that all metadata, in particular `required_binaries` and `users_who_can_read`
57    /// MUST NOT change with a change in versioning. This method is designed only for
58    /// changing the on-the-wire representation of an object, not for changing its semantics.
59    ///
60    /// Semantics changes should happen by sending an "upgrade" event to the object, and
61    /// if cleanup is warranted then performing mass object recreation on the server afterwise.
62    #[allow(unused_variables)]
63    fn from_old_snapshot(version: i32, data: serde_json::Value) -> anyhow::Result<Self> {
64        unimplemented!()
65    }
66
67    fn can_create<'a, C: CanDoCallbacks>(
68        &'a self,
69        user: User,
70        self_id: ObjectId,
71        db: &'a C,
72    ) -> impl 'a + waaaa::Future<Output = crate::Result<bool>>;
73
74    /// Note that permissions are always checked with the latest version of the object on the server.
75    /// So, due to this, CRDB objects are not strictly speaking a CRDT. However, it is required to do
76    /// so for security, because otherwise a user who lost permissions would still be allowed to
77    /// submit events antidated to before the permission loss, which would be bad as users could
78    /// re-grant themselves permissions.
79    fn can_apply<'a, C: CanDoCallbacks>(
80        &'a self,
81        user: User,
82        self_id: ObjectId,
83        event: &'a Self::Event,
84        db: &'a C,
85    ) -> impl 'a + waaaa::Future<Output = crate::Result<bool>>;
86
87    /// Note that `db.get` calls will be cached. So:
88    /// - Use `db.get` as little as possible, to avoid useless cache thrashing
89    /// - Make sure to always read objects in a given order. You should consider all your objects as
90    ///   forming a DAG, and each object's `users_who_can_read` function should:
91    ///   - Only ever operate on a topological sort of the DAG
92    ///   - Only call `db.get` on objects after this object on the topological sort
93    ///
94    ///   Failing to do this might lead to deadlocks within the database, which will result in internal
95    ///   server errors from postgresql.
96    ///   For example, if you have A -> B -> C and A -> C, A's `users_who_can_read` should first call
97    ///   `get` on `B` before calling it on `C`, because otherwise B could be running the same function
98    ///   on `C` and causing a deadlock.
99    ///   Similarly, if A and B both depend on C and D, then `users_who_can_read` for A and B should
100    ///   always lock C and D in the same order, to avoid deadlocks.
101    ///
102    /// In other words, you should consider `db.get()` as taking a lock on the obtained object: there
103    /// must exist a total order for which the vector, consisting of `self` and then all the `C::get`
104    /// calls in-order, is sorted.
105    ///
106    /// In particular, any recursive call of `users_who_can_read` is most likely wrong.
107    fn users_who_can_read<'a, C: CanDoCallbacks>(
108        &'a self,
109        db: &'a C,
110    ) -> impl 'a + waaaa::Future<Output = crate::Result<HashSet<User>>>;
111
112    fn apply(&mut self, self_id: DbPtr<Self>, event: &Self::Event);
113
114    // TODO(misc-low): replace this boilerplate by some serialization dark magic to auto-detect all the fields?
115    // This would be like-ish what we do for SearchableString, except we'd need some more thinking,
116    // eg. a custom Serializer that'd collect only the _crdb-bin-ptr. Also we'd still need "regular"
117    // serialization to be transparent, because users could query() on them.
118    fn required_binaries(&self) -> Vec<BinPtr>;
119}