classification.rs (7601B)
1 use crate::{ 2 GroupLimitsConfig, 3 errors::GroupError, 4 event_view::GroupEventView, 5 ids::GroupId, 6 kinds::{is_moderation_kind, is_relay_generated_kind, is_user_request_kind}, 7 tags::{GroupTagName, extract_group_tag, has_group_identity_tag, require_group_tag}, 8 }; 9 use tangle_protocol::Kind; 10 11 #[derive(Debug, Clone, PartialEq, Eq)] 12 pub enum GroupEventClass { 13 NonGroup, 14 Normal { group_id: GroupId }, 15 Moderation { kind: Kind, group_id: GroupId }, 16 RelayGeneratedSnapshot { kind: Kind, group_id: GroupId }, 17 } 18 19 impl GroupEventClass { 20 pub fn group_id(&self) -> Option<&GroupId> { 21 match self { 22 Self::NonGroup => None, 23 Self::Normal { group_id } 24 | Self::Moderation { group_id, .. } 25 | Self::RelayGeneratedSnapshot { group_id, .. } => Some(group_id), 26 } 27 } 28 29 pub fn is_group(&self) -> bool { 30 !matches!(self, Self::NonGroup) 31 } 32 } 33 34 pub fn classify_group_event( 35 event: &(impl GroupEventView + ?Sized), 36 limits: GroupLimitsConfig, 37 ) -> Result<GroupEventClass, GroupError> { 38 let kind = event.kind()?; 39 if is_relay_generated_kind(kind) { 40 let group_id = require_group_tag(event, GroupTagName::D, limits)? 41 .group_id() 42 .clone(); 43 return Ok(GroupEventClass::RelayGeneratedSnapshot { kind, group_id }); 44 } 45 if is_moderation_kind(kind) { 46 let group_id = require_group_tag(event, GroupTagName::H, limits)? 47 .group_id() 48 .clone(); 49 return Ok(GroupEventClass::Moderation { kind, group_id }); 50 } 51 if is_user_request_kind(kind) { 52 let group_id = require_group_tag(event, GroupTagName::H, limits)? 53 .group_id() 54 .clone(); 55 return Ok(GroupEventClass::Normal { group_id }); 56 } 57 if has_group_identity_tag(event)? 58 && let Some(group_tag) = extract_group_tag(event, GroupTagName::H, limits)? 59 { 60 return Ok(GroupEventClass::Normal { 61 group_id: group_tag.group_id().clone(), 62 }); 63 } 64 Ok(GroupEventClass::NonGroup) 65 } 66 67 #[cfg(test)] 68 mod tests { 69 use super::{GroupEventClass, classify_group_event}; 70 use crate::{ 71 GroupErrorKind, GroupLimitsConfig, KIND_GROUP_CREATE_GROUP, KIND_GROUP_JOIN_REQUEST, 72 KIND_GROUP_METADATA, KIND_GROUP_PUT_USER, 73 }; 74 use pocket_types::Event as PocketEvent; 75 use tangle_protocol::{ 76 Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent, 77 event_to_value, 78 }; 79 80 #[test] 81 fn classifies_non_group_normal_moderation_and_relay_generated_events() { 82 assert_eq!( 83 classify_group_event(&event(1, Vec::new()), GroupLimitsConfig::default()) 84 .expect("non-group"), 85 GroupEventClass::NonGroup 86 ); 87 assert_eq!( 88 classify_group_event( 89 &event(1, vec![Tag::from_parts("h", &["Farm"]).expect("h")]), 90 GroupLimitsConfig::default() 91 ) 92 .expect("normal"), 93 GroupEventClass::Normal { 94 group_id: crate::GroupId::new("Farm").expect("group") 95 } 96 ); 97 assert!(matches!( 98 classify_group_event( 99 &event( 100 KIND_GROUP_PUT_USER, 101 vec![Tag::from_parts("h", &["Farm"]).expect("h")] 102 ), 103 GroupLimitsConfig::default() 104 ) 105 .expect("moderation"), 106 GroupEventClass::Moderation { kind, group_id } 107 if kind.as_u32() == KIND_GROUP_PUT_USER && group_id.as_str() == "Farm" 108 )); 109 assert!(matches!( 110 classify_group_event( 111 &event( 112 KIND_GROUP_METADATA, 113 vec![Tag::from_parts("d", &["Farm"]).expect("d")] 114 ), 115 GroupLimitsConfig::default() 116 ) 117 .expect("relay generated"), 118 GroupEventClass::RelayGeneratedSnapshot { kind, group_id } 119 if kind.as_u32() == KIND_GROUP_METADATA && group_id.as_str() == "Farm" 120 )); 121 assert!(matches!( 122 classify_group_event( 123 &event( 124 KIND_GROUP_CREATE_GROUP, 125 vec![Tag::from_parts("h", &["Farm"]).expect("h")] 126 ), 127 GroupLimitsConfig::default() 128 ) 129 .expect("create"), 130 GroupEventClass::Moderation { kind, .. } if kind.as_u32() == KIND_GROUP_CREATE_GROUP 131 )); 132 assert!(matches!( 133 classify_group_event( 134 &event( 135 KIND_GROUP_JOIN_REQUEST, 136 vec![Tag::from_parts("h", &["Farm"]).expect("h")] 137 ), 138 GroupLimitsConfig::default() 139 ) 140 .expect("join"), 141 GroupEventClass::Normal { group_id } if group_id.as_str() == "Farm" 142 )); 143 } 144 145 #[test] 146 fn d_tags_do_not_make_regular_addressable_events_group_events() { 147 assert_eq!( 148 classify_group_event( 149 &event(30_001, vec![Tag::from_parts("d", &["note"]).expect("d")]), 150 GroupLimitsConfig::default() 151 ) 152 .expect("event"), 153 GroupEventClass::NonGroup 154 ); 155 } 156 157 #[test] 158 fn classifies_pocket_events_through_event_view() { 159 let event = event( 160 KIND_GROUP_PUT_USER, 161 vec![Tag::from_parts("h", &["Farm"]).expect("h")], 162 ); 163 let mut buffer = vec![0; 4096]; 164 let pocket = pocket_event(&event, &mut buffer); 165 166 assert!(matches!( 167 classify_group_event(pocket, GroupLimitsConfig::default()).expect("pocket"), 168 GroupEventClass::Moderation { kind, group_id } 169 if kind.as_u32() == KIND_GROUP_PUT_USER && group_id.as_str() == "Farm" 170 )); 171 } 172 173 #[test] 174 fn required_h_and_d_tag_rules_are_strict() { 175 assert_eq!( 176 classify_group_event( 177 &event(KIND_GROUP_PUT_USER, Vec::new()), 178 GroupLimitsConfig::default() 179 ) 180 .expect_err("missing h") 181 .kind(), 182 GroupErrorKind::MissingGroupTag 183 ); 184 assert_eq!( 185 classify_group_event( 186 &event(KIND_GROUP_JOIN_REQUEST, Vec::new()), 187 GroupLimitsConfig::default() 188 ) 189 .expect_err("missing h") 190 .kind(), 191 GroupErrorKind::MissingGroupTag 192 ); 193 assert_eq!( 194 classify_group_event( 195 &event( 196 KIND_GROUP_METADATA, 197 vec![Tag::from_parts("h", &["Farm"]).expect("h")] 198 ), 199 GroupLimitsConfig::default() 200 ) 201 .expect_err("missing d") 202 .kind(), 203 GroupErrorKind::MissingGroupTag 204 ); 205 } 206 207 fn event(kind: u32, tags: Vec<Tag>) -> Event { 208 Event::new( 209 EventId::new(&"0".repeat(64)).expect("id"), 210 UnsignedEvent::new( 211 PublicKeyHex::new(&"1".repeat(64)).expect("pubkey"), 212 UnixTimestamp::new(1), 213 Kind::new(kind.into()).expect("kind"), 214 tags, 215 "", 216 ), 217 SignatureHex::new(&"2".repeat(128)).expect("sig"), 218 ) 219 } 220 221 fn pocket_event<'a>(event: &Event, buffer: &'a mut [u8]) -> &'a PocketEvent { 222 let raw = event_to_value(event).to_string(); 223 let (_, pocket) = PocketEvent::from_json(raw.as_bytes(), buffer).expect("pocket"); 224 pocket 225 } 226 }