tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

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 }