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}