cloudillo_core/
profile_visibility.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16pub enum CommunityRole {
17 Supporter = 1,
18 Contributor = 2,
19 Moderator = 3,
20 Leader = 4,
21}
22
23impl CommunityRole {
24 pub const ALL: &'static [&'static str] = &["supporter", "contributor", "moderator", "leader"];
28
29 pub fn parse(s: &str) -> Option<Self> {
31 match s {
32 "supporter" => Some(Self::Supporter),
33 "contributor" => Some(Self::Contributor),
34 "moderator" => Some(Self::Moderator),
35 "leader" => Some(Self::Leader),
36 _ => None,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum SectionVisibility {
44 Public,
45 Verified,
46 Follower,
47 Connected,
48 Role(CommunityRole),
49}
50
51impl SectionVisibility {
52 pub fn parse(s: &str) -> Option<Self> {
55 match s {
56 "public" | "world" => Some(Self::Public),
57 "verified" => Some(Self::Verified),
58 "follower" => Some(Self::Follower),
59 "connected" => Some(Self::Connected),
60 other => CommunityRole::parse(other).map(Self::Role),
61 }
62 }
63}
64
65#[allow(clippy::struct_excessive_bools)]
67#[derive(Debug, Clone, Copy)]
68pub struct RequesterTier {
69 pub is_owner: bool,
70 pub is_authenticated: bool,
71 pub follows_tenant: bool,
73 pub connected_to_tenant: bool,
75 pub max_role: Option<CommunityRole>,
77}
78
79impl RequesterTier {
80 pub fn anonymous() -> Self {
82 Self {
83 is_owner: false,
84 is_authenticated: false,
85 follows_tenant: false,
86 connected_to_tenant: false,
87 max_role: None,
88 }
89 }
90
91 pub fn can_view(self, required: SectionVisibility) -> bool {
93 if self.is_owner {
94 return true;
95 }
96 match required {
97 SectionVisibility::Public => true,
98 SectionVisibility::Verified => self.is_authenticated,
99 SectionVisibility::Follower => self.follows_tenant || self.connected_to_tenant,
100 SectionVisibility::Connected => self.connected_to_tenant,
101 SectionVisibility::Role(r) => {
102 self.is_authenticated && self.max_role.is_some_and(|m| m >= r)
103 }
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn parse_known_labels() {
114 assert_eq!(SectionVisibility::parse("public"), Some(SectionVisibility::Public));
115 assert_eq!(SectionVisibility::parse("world"), Some(SectionVisibility::Public));
116 assert_eq!(SectionVisibility::parse("verified"), Some(SectionVisibility::Verified));
117 assert_eq!(SectionVisibility::parse("follower"), Some(SectionVisibility::Follower));
118 assert_eq!(SectionVisibility::parse("connected"), Some(SectionVisibility::Connected));
119 assert_eq!(
120 SectionVisibility::parse("supporter"),
121 Some(SectionVisibility::Role(CommunityRole::Supporter)),
122 );
123 assert_eq!(
124 SectionVisibility::parse("contributor"),
125 Some(SectionVisibility::Role(CommunityRole::Contributor)),
126 );
127 assert_eq!(
128 SectionVisibility::parse("moderator"),
129 Some(SectionVisibility::Role(CommunityRole::Moderator)),
130 );
131 assert_eq!(
132 SectionVisibility::parse("leader"),
133 Some(SectionVisibility::Role(CommunityRole::Leader)),
134 );
135 }
136
137 #[test]
138 fn parse_unknown_returns_none() {
139 assert_eq!(SectionVisibility::parse(""), None);
140 assert_eq!(SectionVisibility::parse("banana"), None);
141 assert_eq!(SectionVisibility::parse("Public"), None);
142 assert_eq!(SectionVisibility::parse("LEADER"), None);
143 }
144
145 #[test]
146 fn parse_role_known_and_unknown() {
147 assert_eq!(CommunityRole::parse("supporter"), Some(CommunityRole::Supporter));
148 assert_eq!(CommunityRole::parse("leader"), Some(CommunityRole::Leader));
149 assert_eq!(CommunityRole::parse("admin"), None);
150 assert_eq!(CommunityRole::parse(""), None);
151 }
152
153 fn tier_anon() -> RequesterTier {
154 RequesterTier::anonymous()
155 }
156
157 fn tier_verified() -> RequesterTier {
158 RequesterTier { is_authenticated: true, ..RequesterTier::anonymous() }
159 }
160
161 fn tier_follower() -> RequesterTier {
162 RequesterTier { is_authenticated: true, follows_tenant: true, ..RequesterTier::anonymous() }
163 }
164
165 fn tier_connected() -> RequesterTier {
166 RequesterTier {
167 is_authenticated: true,
168 connected_to_tenant: true,
169 ..RequesterTier::anonymous()
170 }
171 }
172
173 fn tier_role(role: CommunityRole) -> RequesterTier {
174 RequesterTier { is_authenticated: true, max_role: Some(role), ..RequesterTier::anonymous() }
175 }
176
177 fn tier_owner() -> RequesterTier {
178 RequesterTier { is_owner: true, ..RequesterTier::anonymous() }
179 }
180
181 #[test]
182 fn anonymous_can_only_see_public() {
183 let t = tier_anon();
184 assert!(t.can_view(SectionVisibility::Public));
185 assert!(!t.can_view(SectionVisibility::Verified));
186 assert!(!t.can_view(SectionVisibility::Follower));
187 assert!(!t.can_view(SectionVisibility::Connected));
188 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
189 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Leader)));
190 }
191
192 #[test]
193 fn verified_no_role_blocked_from_role_gates() {
194 let t = tier_verified();
195 assert!(t.can_view(SectionVisibility::Public));
196 assert!(t.can_view(SectionVisibility::Verified));
197 assert!(!t.can_view(SectionVisibility::Follower));
198 assert!(!t.can_view(SectionVisibility::Connected));
199 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
200 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Contributor)));
201 }
202
203 #[test]
204 fn follower_satisfies_follower_only() {
205 let t = tier_follower();
206 assert!(t.can_view(SectionVisibility::Follower));
207 assert!(!t.can_view(SectionVisibility::Connected));
208 assert!(t.can_view(SectionVisibility::Verified));
209 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
210 }
211
212 #[test]
213 fn connected_satisfies_follower_and_connected() {
214 let t = tier_connected();
215 assert!(t.can_view(SectionVisibility::Follower));
216 assert!(t.can_view(SectionVisibility::Connected));
217 assert!(t.can_view(SectionVisibility::Verified));
218 }
219
220 #[test]
221 fn supporter_can_see_supporter_only() {
222 let t = tier_role(CommunityRole::Supporter);
223 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
224 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Contributor)));
225 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Moderator)));
226 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Leader)));
227 }
228
229 #[test]
230 fn contributor_meets_contributor_and_below() {
231 let t = tier_role(CommunityRole::Contributor);
232 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
233 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Contributor)));
234 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Moderator)));
235 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Leader)));
236 }
237
238 #[test]
239 fn moderator_meets_moderator_and_below() {
240 let t = tier_role(CommunityRole::Moderator);
241 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
242 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Contributor)));
243 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Moderator)));
244 assert!(!t.can_view(SectionVisibility::Role(CommunityRole::Leader)));
245 }
246
247 #[test]
248 fn leader_meets_all_roles() {
249 let t = tier_role(CommunityRole::Leader);
250 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
251 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Contributor)));
252 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Moderator)));
253 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Leader)));
254 }
255
256 #[test]
257 fn owner_sees_everything() {
258 let t = tier_owner();
259 assert!(t.can_view(SectionVisibility::Public));
260 assert!(t.can_view(SectionVisibility::Verified));
261 assert!(t.can_view(SectionVisibility::Follower));
262 assert!(t.can_view(SectionVisibility::Connected));
263 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Supporter)));
264 assert!(t.can_view(SectionVisibility::Role(CommunityRole::Leader)));
265 }
266}
267
268