1use ruma_common::{MxcUri, UserId};
2
3use super::MembershipState;
4
5#[derive(Clone, Debug)]
7pub struct MembershipDetails<'a> {
8 pub(crate) avatar_url: Option<&'a MxcUri>,
9 pub(crate) displayname: Option<&'a str>,
10 pub(crate) membership: &'a MembershipState,
11}
12
13#[derive(Clone, Debug)]
15#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
16pub enum MembershipChange<'a> {
17 None,
19
20 Error,
22
23 Joined,
25
26 Left,
28
29 Banned,
31
32 Unbanned,
34
35 Kicked,
37
38 Invited,
40
41 KickedAndBanned,
43
44 InvitationAccepted,
46
47 InvitationRejected,
49
50 InvitationRevoked,
52
53 Knocked,
55
56 KnockAccepted,
58
59 KnockRetracted,
61
62 KnockDenied,
64
65 ProfileChanged {
67 displayname_change: Option<Change<Option<&'a str>>>,
69
70 avatar_url_change: Option<Change<Option<&'a MxcUri>>>,
72 },
73
74 NotImplemented,
76}
77
78#[derive(Clone, Debug)]
80#[allow(clippy::exhaustive_structs)]
81pub struct Change<T> {
82 pub old: T,
84
85 pub new: T,
87}
88
89impl<T: PartialEq> Change<T> {
90 fn new(old: T, new: T) -> Option<Self> {
91 if old == new {
92 None
93 } else {
94 Some(Self { old, new })
95 }
96 }
97}
98
99pub(super) fn membership_change<'a>(
106 details: MembershipDetails<'a>,
107 prev_details: Option<MembershipDetails<'a>>,
108 sender: &UserId,
109 state_key: &UserId,
110) -> MembershipChange<'a> {
111 use MembershipChange as Ch;
112 use MembershipState as St;
113
114 let prev_details = match prev_details {
115 Some(prev) => prev,
116 None => MembershipDetails { avatar_url: None, displayname: None, membership: &St::Leave },
117 };
118
119 match (&prev_details.membership, &details.membership) {
120 (St::Leave, St::Join) => Ch::Joined,
121 (St::Invite, St::Join) => Ch::InvitationAccepted,
122 (St::Invite, St::Leave) if sender == state_key => Ch::InvitationRejected,
123 (St::Invite, St::Leave) => Ch::InvitationRevoked,
124 (St::Invite, St::Ban) | (St::Leave, St::Ban) | (St::Knock, St::Ban) => Ch::Banned,
125 (St::Join, St::Invite)
126 | (St::Ban, St::Invite)
127 | (St::Ban, St::Join)
128 | (St::Join, St::Knock)
129 | (St::Invite, St::Knock)
130 | (St::Ban, St::Knock)
131 | (St::Knock, St::Join) => Ch::Error,
132 (St::Join, St::Join)
133 if sender == state_key
134 && (prev_details.displayname != details.displayname
135 || prev_details.avatar_url != details.avatar_url) =>
136 {
137 Ch::ProfileChanged {
138 displayname_change: Change::new(prev_details.displayname, details.displayname),
139 avatar_url_change: Change::new(prev_details.avatar_url, details.avatar_url),
140 }
141 }
142 (St::Join, St::Leave) if sender == state_key => Ch::Left,
143 (St::Join, St::Leave) => Ch::Kicked,
144 (St::Join, St::Ban) => Ch::KickedAndBanned,
145 (St::Leave, St::Invite) => Ch::Invited,
146 (St::Ban, St::Leave) => Ch::Unbanned,
147 (St::Leave, St::Knock) => Ch::Knocked,
148 (St::Knock, St::Invite) => Ch::KnockAccepted,
149 (St::Knock, St::Leave) if sender == state_key => Ch::KnockRetracted,
150 (St::Knock, St::Leave) => Ch::KnockDenied,
151 (a, b) if a == b => Ch::None,
152 _ => Ch::NotImplemented,
153 }
154}