rcommunity_core/client/
mod.rs

1use std::marker::PhantomData;
2
3use crate::{
4    error::Result,
5    markers::{ItemType, ReactionType, Storable, UserType},
6    store::{Store, Transaction},
7};
8
9pub struct Reaction<TU, TI, TR>
10where
11    TU: UserType,
12    TI: ItemType,
13    TR: ReactionType,
14{
15    pub id: String,
16    pub user: TU,
17    pub item: TI,
18    pub reaction: TR,
19}
20
21#[derive(Debug)]
22pub struct UserItemUnboundedReactionClient<
23    'store,
24    TS: Store,
25    TU: UserType,
26    TI: ItemType,
27    TR: ReactionType,
28> {
29    store: &'store mut TS,
30    user: TU,
31    item: TI,
32    reaction_type: PhantomData<TR>,
33}
34
35impl<'store, TS: Store, TU: UserType, TI: ItemType, TR: ReactionType>
36    UserItemUnboundedReactionClient<'store, TS, TU, TI, TR>
37{
38    /// Create a new reaction.
39    ///
40    /// # Errors
41    /// Will return error when internal store failed.
42    pub async fn react(&mut self, reaction: impl Into<TR>) -> Result<Reaction<TU, TI, TR>> {
43        let mut txn = self.store.begin_txn().await?;
44        let r = reaction.into();
45        let rid = uuid::Uuid::new_v4().to_string(); // TODO: keep Uuid type
46        r.store_reaction(&mut txn, &rid, &self.user, &self.item)
47            .await?;
48        r.store_unique_index(&mut txn, &rid, &self.user, &self.item)
49            .await?;
50
51        txn.commit().await?;
52
53        Ok(Reaction {
54            id: rid,
55            user: self.user.clone(),
56            item: self.item.clone(),
57            reaction: r,
58        })
59    }
60}
61
62#[cfg(test)]
63mod test {
64    use std::marker::PhantomData;
65
66    use crate::{
67        markers::{ItemType, ReactionType, Serializable, UserType, ID},
68        store::{memory::MemoryStore, Store, Transaction},
69    };
70
71    use super::UserItemUnboundedReactionClient;
72
73    // test types
74    #[derive(Clone, Debug)]
75    struct User(String);
76    impl ID for User {
77        fn id(&self) -> &str {
78            &self.0
79        }
80    }
81    impl UserType for User {}
82    #[derive(Clone, Debug)]
83    struct Item(String);
84    impl ID for Item {
85        fn id(&self) -> &str {
86            &self.0
87        }
88    }
89    impl ItemType for Item {}
90
91    #[derive(Copy, Clone, Debug)]
92    enum Vote {
93        Upvote,
94        Downvote,
95    }
96    impl Serializable for Vote {
97        fn serialize(&self) -> String {
98            match *self {
99                Vote::Upvote => "Upvote",
100                Vote::Downvote => "Downvote",
101            }
102            .into()
103        }
104    }
105    impl ReactionType for Vote {}
106
107    #[derive(Clone, Debug)]
108    struct Comment(String);
109    impl ReactionType for Comment {}
110    impl ItemType for Comment {}
111    impl ID for Comment {
112        fn id(&self) -> &str {
113            &self.0
114        }
115    }
116
117    #[tokio::test]
118    async fn test_reaction() {
119        let mut store = MemoryStore::default();
120        let txn = store.begin_txn().await.unwrap();
121
122        let mut client = UserItemUnboundedReactionClient {
123            store: &mut store,
124            user: User("1000".into()),
125            item: Item("2000".into()),
126            reaction_type: PhantomData::<Vote>,
127        };
128        let vote = client.react(Vote::Upvote).await.unwrap();
129
130        // vote tests
131        let value = txn
132            .get(format!("Vote:User:1000:Item:2000:{}", vote.id))
133            .await
134            .unwrap()
135            .unwrap();
136        assert_eq!(&value, "Upvote");
137
138        let value = txn
139            .get("Vote:User:1000:Item:2000:Upvote".into())
140            .await
141            .unwrap();
142        assert!(value.is_none());
143
144        let vote = client.react(Vote::Downvote).await.unwrap();
145        let value = txn
146            .get(format!("Vote:User:1000:Item:2000:{}", vote.id))
147            .await
148            .unwrap()
149            .unwrap();
150        assert_eq!(&value, "Downvote");
151
152        // comment tests
153        let mut client = UserItemUnboundedReactionClient {
154            store: &mut store,
155            user: User("1000".into()),
156            item: Item("2000".into()),
157            reaction_type: PhantomData::<Comment>,
158        };
159        let comment = client.react(Comment("3000".into())).await.unwrap();
160
161        let value = txn
162            .get(format!("Comment:User:1000:Item:2000:{}", comment.id))
163            .await
164            .unwrap()
165            .unwrap();
166        assert_eq!(&value, "Comment:3000");
167        let value = txn
168            .get("Comment:User:1000:Item:2000:Comment:3000".into())
169            .await
170            .unwrap()
171            .unwrap();
172        assert_eq!(value, comment.id);
173    }
174}