Skip to main content

max / makenotwork

3.1 KB · 100 lines History Blame Raw
1 //! Identifier newtypes shared by both consumers.
2 //!
3 //! Both apps key users and rooms by UUID (Multithreaded's `users.mnw_account_id`
4 //! and `communities.id`; the MNW server's UUID domain entities), so the crate can
5 //! name those concretely without constraining either schema.
6 //!
7 //! Messages are the exception. `MessageId` is a monotonic `i64` rather than a
8 //! UUID because the reconnect cursor (`?after=<id>`) needs a total order, and
9 //! because chat messages are log-shaped, which is the case the MNW server already
10 //! spells `BIGINT GENERATED ALWAYS AS IDENTITY`.
11
12 use serde::{Deserialize, Serialize};
13 use uuid::Uuid;
14
15 macro_rules! uuid_id {
16 ($name:ident, $doc:literal) => {
17 #[doc = $doc]
18 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19 #[serde(transparent)]
20 pub struct $name(pub Uuid);
21
22 impl From<Uuid> for $name {
23 fn from(id: Uuid) -> Self {
24 Self(id)
25 }
26 }
27
28 impl From<$name> for Uuid {
29 fn from(id: $name) -> Self {
30 id.0
31 }
32 }
33
34 impl std::fmt::Display for $name {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 self.0.fmt(f)
37 }
38 }
39 };
40 }
41
42 uuid_id!(
43 RoomId,
44 "A chat room. One per community in Multithreaded; one per stream in MNW."
45 );
46 uuid_id!(
47 UserId,
48 "A chat participant, in the host app's user namespace."
49 );
50
51 /// A single message, ordered. Doubles as the reconnect cursor.
52 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
53 #[serde(transparent)]
54 pub struct MessageId(pub i64);
55
56 impl std::fmt::Display for MessageId {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 self.0.fmt(f)
59 }
60 }
61
62 /// Client-generated send nonce, echoed back so an optimistically rendered
63 /// message reconciles instead of appearing twice.
64 ///
65 /// Client-supplied and therefore untrusted: it is opaque to the server, capped
66 /// in length, and only ever compared for equality. It never reaches a query.
67 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
68 #[serde(transparent)]
69 pub struct Nonce(pub String);
70
71 impl Nonce {
72 /// Longest accepted nonce. A client only needs enough to disambiguate its own
73 /// in-flight sends; anything longer is someone probing.
74 pub const MAX_LEN: usize = 64;
75
76 /// Returns `None` if the nonce is empty or over [`Nonce::MAX_LEN`].
77 pub fn parse(raw: impl Into<String>) -> Option<Self> {
78 let raw = raw.into();
79 (!raw.is_empty() && raw.len() <= Self::MAX_LEN).then_some(Self(raw))
80 }
81 }
82
83 #[cfg(test)]
84 mod tests {
85 use super::*;
86
87 #[test]
88 fn nonce_rejects_empty_and_overlong() {
89 assert!(Nonce::parse("").is_none());
90 assert!(Nonce::parse("a").is_some());
91 assert!(Nonce::parse("x".repeat(Nonce::MAX_LEN)).is_some());
92 assert!(Nonce::parse("x".repeat(Nonce::MAX_LEN + 1)).is_none());
93 }
94
95 #[test]
96 fn message_ids_order_by_value() {
97 assert!(MessageId(1) < MessageId(2));
98 }
99 }
100