nostr_types/types/
event_kind.rs

1use serde::de::Error as DeError;
2use serde::de::{Deserializer, Visitor};
3use serde::ser::Serializer;
4use serde::{Deserialize, Serialize};
5#[cfg(feature = "speedy")]
6use speedy::{Context, Readable, Reader, Writable, Writer};
7use std::convert::From;
8use std::fmt;
9
10macro_rules! define_event_kinds {
11    ($($comment:expr, $display:expr, $name:ident = $value:expr),*) => {
12        /// A kind of Event
13        #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
14        #[repr(u32)]
15        pub enum EventKind {
16            $(
17                #[doc = $comment]
18                $name = $value,
19            )*
20            /// Job Request (NIP-90) 5000-5999
21            JobRequest(u32),
22            /// Job Result (NIP-90) 6000-6999
23            JobResult(u32),
24            /// Group control events (NIP-29) 9000-9030
25            GroupControl(u32),
26            /// Relay-specific replaceable event
27            Replaceable(u32),
28            /// Ephemeral event, sent to all clients with matching filters and should not be stored
29            Ephemeral(u32),
30            /// Group Metadata events
31            GroupMetadata(u32),
32            /// Something else?
33            Other(u32),
34        }
35
36        static WELL_KNOWN_KINDS: &[EventKind] = &[
37            $($name,)*
38        ];
39
40        impl fmt::Display for EventKind {
41            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42                match *self {
43                    $($name => write!(f, $display),)*
44                    JobRequest(u) => write!(f, "Job Request ({})", u),
45                    JobResult(u) => write!(f, "Job Result ({})", u),
46                    GroupControl(u) => write!(f, "Group Control ({})", u),
47                    Replaceable(u) => write!(f, "Replaceable ({})", u),
48                    Ephemeral(u) => write!(f, "Ephemeral ({})", u),
49                    GroupMetadata(u) => write!(f, "Group Metadata ({})", u),
50                    Other(u) => write!(f, "Other ({})", u),
51                }
52            }
53        }
54
55        impl From<u32> for EventKind {
56            fn from(u: u32) -> Self {
57                match u {
58                    $($value => $name,)*
59                    x if (5_000..5_999).contains(&x) => JobRequest(x),
60                    x if (6_000..6_999).contains(&x) => JobResult(x),
61                    x if (9_000..9_030).contains(&x) => GroupControl(x),
62                    x if (10_000..20_000).contains(&x) => Replaceable(x),
63                    x if (20_000..30_000).contains(&x) => Ephemeral(x),
64                    x if (39_000..39_009).contains(&x) => GroupMetadata(x),
65                    x => Other(x),
66                }
67            }
68        }
69
70        impl From<EventKind> for u32 {
71            fn from(e: EventKind) -> u32 {
72                match e {
73                    $($name => $value,)*
74                    JobRequest(u) => u,
75                    JobResult(u) => u,
76                    GroupControl(u) => u,
77                    Replaceable(u) => u,
78                    Ephemeral(u) => u,
79                    GroupMetadata(u) => u,
80                    Other(u) => u,
81                }
82            }
83        }
84    };
85}
86
87define_event_kinds!(
88    "Event sets the metadata associated with a public key (NIP-01)",
89    "User Metadata",
90    Metadata = 0,
91
92    "Event is a text note (NIP-01)",
93    "Short Text Note",
94    TextNote = 1,
95
96    "Event contains a relay URL which the author recommends",
97    "Recommend Relay",
98    RecommendRelay = 2,
99
100    "Event contains tags which represent the authors contacts including the authors pet names for them (NIP-02)",
101    "Follows",
102    ContactList = 3,
103
104    "Event is an encrypted direct message (NIP-04)",
105    "Encrypted Direct Messages",
106    EncryptedDirectMessage = 4,
107
108    "Event is an authors request to delete previous events (NIP-09)",
109    "Event Deletion Request",
110    EventDeletion = 5,
111
112    "Repost (NIP-18)",
113    "Repost",
114    Repost = 6,
115
116    "Event is a reaction to a `TextNote` event (NIP-25)",
117    "Reaction",
118    Reaction = 7,
119
120    "Badge Award (NIP-58)",
121    "Badge Award",
122    BadgeAward = 8,
123
124    "Group Chat Message (NIP-29)",
125    "Group Chat Message",
126    GroupChatMessage = 9,
127
128    "Group Chat Threaded Reply (NIP-29)",
129    "Group Chat Threaded Reply",
130    GroupChatThreadedReply = 10,
131
132    "Group Chat Thread (NIP-29)",
133    "Group Chat Thread",
134    GroupChatThread = 11,
135
136    "Group Chat Reply (NIP-29)",
137    "Group Chat Reply",
138    GroupChatReply = 12,
139
140    "Seal (NIP-59 PR 716)",
141    "Seal",
142    Seal = 13,
143
144    "Chat Message / DM (NIP-24 PR 686)",
145    "Direct Message",
146    DmChat = 14,
147
148    "Generic Repost (NIP-18)",
149    "Generic Repost",
150    GenericRepost = 16,
151
152    "Reaction to a website (NIP-25)",
153    "Reaction to a website",
154    ReactionToWebsite = 17,
155
156    "Picture (NIP-68)",
157    "Picture",
158    Picture = 20,
159
160    "Event creates a public channel (NIP-28)",
161    "Channel Creation",
162    ChannelCreation = 40,
163
164    "Event sets metadata on a public channel (NIP-28)",
165    "Channel Metadata",
166    ChannelMetadata = 41,
167
168    "Event creates a message on a public channel (NIP-28)",
169    "Channel Message",
170    ChannelMessage = 42,
171
172    "Event hides a message on a public channel (NIP-28)",
173    "Channel Hide Message",
174    ChannelHideMessage = 43,
175
176    "Event mutes a user on a public channel (NIP-28)",
177    "Channel Mute User",
178    ChannelMuteUser = 44,
179
180    "Request to Vanish (NIP-62)",
181    "Request to Vanish",
182    RequestToVanish = 62,
183
184    "Chess (PGN) (NIP-64)",
185    "Chess (PGN)",
186    ChessPgn = 64,
187
188    "Wiki article merge requests (NIP-54)",
189    "Wiki Merge Requests",
190    WikiMergeRequest = 818,
191
192    "Bid (NIP-15)",
193    "Bid",
194    Bid = 1021,
195
196    "Bid Confirmation (NIP-15)",
197    "Bid Confirmation",
198    BidConfirmation = 1022,
199
200    "Open Timestamps (NIP-03)",
201    "Open Timestamp",
202    Timestamp = 1040,
203
204    "Gift Wrap (NIP-59 PR 716)",
205    "Gift Wrap",
206    GiftWrap = 1059,
207
208    "File Metadata (NIP-94)",
209    "File Metadata",
210    FileMetadata = 1063,
211
212    "Comment (NIP-22 PR #1233)" ,
213    "Comment",
214    Comment = 1111,
215
216    "Live Chat Message (NIP-53)",
217    "Live Chat Message",
218    LiveChatMessage = 1311,
219
220    "Git Patches (NIP-34)",
221    "Git Patch",
222    Patches = 1617,
223
224    "Git Issue (NIP-34)",
225    "Git Issue",
226    GitIssue = 1621,
227
228    "Replies (NIP-34)",
229    "Git Reply",
230    GitReply = 1622,
231
232    "Status Open  (NIP-34)",
233    "Git Status Open",
234    GitStatusOpen = 1630,
235
236    "Status Applied (NIP-34)",
237    "Git Status Applied",
238    GitStatusApplied = 1631,
239
240    "Status Closed (NIP-34)",
241    "Git Status Closed",
242    GitStatusClosed = 1632,
243
244    "Status Draft (NIP-34)",
245    "Git Status Draft",
246    GitStatusDraft = 1633,
247
248    "Problem Tracker (nostrocket-1971)",
249    "Problem Tracker",
250    ProblemTracker = 1971,
251
252    "Reporting (NIP-56)",
253    "Reporting",
254    Reporting = 1984,
255
256    "Label (NIP-32)",
257    "Label",
258    Label = 1985,
259
260    "Relay reviews",
261    "Relay reviews",
262    RelayReviews = 1986,
263
264    "AI Embeddings",
265    "AI Embeddings",
266    AiEmbeddings = 1987,
267
268    "Torrent",
269    "Torrent",
270    Torrent = 2003,
271
272    "Torrent Comment",
273    "Torrent Comment",
274    TorrentComment = 2004,
275
276    "Coinjoin Pool",
277    "Coinjoin Pool",
278    CoinjoinPool = 2022,
279
280    "Community Post Approval (NIP-72)",
281    "Community Post Approval",
282    CommunityPostApproval = 4550,
283
284    "Job Feedback (NIP-90)",
285    "Job Feedback",
286    JobFeedback = 7000,
287
288    "Zap Goal (NIP-75)",
289    "Zap Goal",
290    ZapGoal = 9041,
291
292    "Tidal Login",
293    "Tidal Login",
294    TidalLogin = 9467,
295
296    "Zap Request",
297    "Zap Request",
298    ZapRequest = 9734,
299
300    "Zap",
301    "Zap",
302    Zap = 9735,
303
304    "Highlights (NIP-84)",
305    "Highlights",
306    Highlights = 9802,
307
308    "Mute List (NIP-51)",
309    "Mute List",
310    MuteList = 10000,
311
312    "Pin List (NIP-51)",
313    "Pin List",
314    PinList = 10001,
315
316    "Relay List Metadata (NIP-65)",
317    "Relay List Metadata",
318    RelayList = 10002,
319
320    "Bookmarks List (NIP-51)",
321    "Bookmarks List",
322    BookmarkList = 10003,
323
324    "Communities List (NIP-51)",
325    "Communities List",
326    CommunityList = 10004,
327
328    "Public Chats List (NIP-51)",
329    "Public Chats List",
330    PublicChatsList = 10005,
331
332    "Blocked Relays List (NIP-51)",
333    "Blocked Relays List",
334    BlockedRelaysList = 10006,
335
336    "Search Relays List (NIP-51)",
337    "Search Relays List",
338    SearchRelaysList = 10007,
339
340    "User Groups (NIP-51, NIP-29)",
341    "User Groups",
342    UserGroups = 10009,
343
344    "Interests List (NIP-51)",
345    "Interests List",
346    InterestsList = 10015,
347
348    "User Emoji List (NIP-51)",
349    "User Emoji List",
350    UserEmojiList = 10030,
351
352    "Relay list to receive DMs (NIP-17)",
353    "DM Relay List",
354    DmRelayList = 10050,
355
356    "User Server List",
357    "User Server List",
358    UserServerList = 10063,
359
360    "File storage server list (NIP-96)",
361    "File Storage Server List",
362    FileStorageServerList = 10096,
363
364    "Wallet Info (NIP-47)",
365    "Wallet Info",
366    WalletInfo = 13194,
367
368    "Lightning Pub RPC (Lightning.Pub)",
369    "Lightning Pub RPC",
370    LightningPubRpc = 21000,
371
372    "Client Authentication (NIP-42)",
373    "Client Authentication",
374    Auth = 22242,
375
376    "Wallet Request (NIP-47)",
377    "Wallet Request",
378    WalletRequest = 23194,
379
380    "Wallet Response (NIP-47)",
381    "Wallet Response",
382    WalletResponse = 23195,
383
384    "Nostr Connect (NIP-46)",
385    "Nostr Connect",
386    NostrConnect = 24133,
387
388    "Blobs stored on mediaservers (Blossom)",
389    "Blossom",
390    Blossom = 24242,
391
392    "HTTP Auth (NIP-98)",
393    "HTTP Auth",
394    HttpAuth = 27235,
395
396    "Categorized People List (NIP-51)",
397    "Follow Sets",
398    FollowSets = 30000,
399
400    "Categorized Bookmark List (NIP-51)",
401    "Generic Lists",
402    GenericSets = 30001,
403
404    "Relay Sets (NIP-51)",
405    "Relay Sets",
406    RelaySets = 30002,
407
408    "Bookmark Sets (NIP-51)",
409    "Bookmark Sets",
410    BookmarkSets = 30003,
411
412    "Curation Sets (NIP-51)",
413    "Curation Sets",
414    CurationSets = 30004,
415
416    "Video Sets (NIP-51)",
417    "Video Sets",
418    VideoSets = 30005,
419
420    "Kind Mute Sets (NIP-51)",
421    "Kind Mute Sets",
422    KindMuteSets = 30007,
423
424    "Profile Badges (NIP-58)",
425    "Profile Badges",
426    ProfileBadges = 30008,
427
428    "Badge Definition (NIP-58)",
429    "Badge Definition",
430    BadgeDefinition = 30009,
431
432    "Interest Sets (NIP-51)",
433    "Interest Sets",
434    InterestSets = 30015,
435
436    "Create or update a stall (NIP-15)",
437    "Create Or Update Stall",
438    CreateUpdateStall = 30017,
439
440    "Create or update a product (NIP-15)",
441    "Create Or Update Product",
442    CreateUpdateProduct = 30018,
443
444    "Marketplace UI/UX (NIP-15)",
445    "Marketplace UI/UX",
446    MarketplaceUi = 30019,
447
448    "Product sold as auction (NIP-15)",
449    "Product Sold As Auction",
450    ProductSoldAuction = 30020,
451
452    "Long-form Content (NIP-23)",
453    "Long-form Content",
454    LongFormContent = 30023,
455
456    "Draft Long-form Content (NIP-23)",
457    "Draft Long-form Content",
458    DraftLongFormContent = 30024,
459
460    "Emoji Sets (NIP-51)",
461    "Emoji Sets",
462    EmojiSets = 30030,
463
464    "Modular Article Header",
465    "Modular Article Header",
466    ModularArticleHedaer = 30040,
467
468    "Modular Article Content",
469    "Modular Article Content",
470    ModularArticleContent = 30041,
471
472    "Release artifact sets (NIP-51)",
473    "Release Artifact Sets",
474    ReleaseArtifactSets = 30063,
475
476    "Application Specific Data, (NIP-78)",
477    "Application Specific Data",
478    AppSpecificData = 30078,
479
480    "Live Event (NIP-53)",
481    "Live Event",
482    LiveEvent = 30311,
483
484    "User Status (NIP-315 PR 737)",
485    "User Statuses",
486    UserStatus = 30315,
487
488    "Classified Listing (NIP-99)",
489    "Classified Listing",
490    ClassifiedListing = 30402,
491
492    "Draft Classified Listing (NIP-99)",
493    "Draft Classified Listing",
494    DraftClassifiedListing = 30403,
495
496    "Repository Announcement (NIP-34)",
497    "Repository Announcement",
498    RepositoryAnnouncement = 30617,
499
500    "Repository State Announcement (NIP-34)",
501    "Repository State Announcement",
502    RepositoryStateAnnouncement = 30618,
503
504    "Wiki Article (NIP-54)",
505    "Wiki Article",
506    WikiArticle = 30818,
507
508    "Redirects",
509    "Redirects",
510    Redirects = 30819,
511
512    "Link Set",
513    "Link Set",
514    LinkSet = 31388,
515
516    "Feed",
517    "Feed",
518    Feed = 31890,
519
520    "Date-Based Calendar Event (NIP-52)",
521    "Date-Based Calendar Event",
522    DateBasedCalendarEvent = 31922,
523
524    "Time-Based Calendar Event (NIP-52)",
525    "Time-Based Calendar Event",
526    TimeBasedCalendarEvent = 31923,
527
528    "Calendar (NIP-52)",
529    "Calendar",
530    Calendar = 31924,
531
532    "Calendar Event RSVP (NIP-52)",
533    "Calendar Event RSVP",
534    CalendarEventRsvp = 31925,
535
536    "Handler Recommendation (NIP-89)",
537    "Handler Recommendation",
538    HandlerRecommendation = 31989,
539
540    "Handler Information (NIP-89)",
541    "Handler Information",
542    HandlerInformation = 31990,
543
544    "Video Event",
545    "Video Event",
546    VideoEvent = 34235,
547
548    "Short-form Portrait Video Event",
549    "Short-Form Portrait Video Event",
550    ShortFormPortraitVideoEvent = 34236,
551
552    "Video View Event",
553    "Video View Event",
554    VideoViewEvent = 34237,
555
556    "Community Definition (NIP-72)",
557    "Community Definition",
558    CommunityDefinition = 34550
559);
560
561use EventKind::*;
562
563impl EventKind {
564    // Mock data for testing
565    #[allow(dead_code)]
566    pub(crate) fn mock() -> EventKind {
567        TextNote
568    }
569
570    /// Is a job request kind
571    pub fn is_job_request(&self) -> bool {
572        let u: u32 = From::from(*self);
573        (5000..=5999).contains(&u)
574    }
575
576    /// Is a job result kind
577    pub fn is_job_result(&self) -> bool {
578        let u: u32 = From::from(*self);
579        (6000..=6999).contains(&u)
580    }
581
582    /// If this event kind is a replaceable event
583    /// NOTE: this INCLUDES parameterized replaceable events
584    pub fn is_replaceable(&self) -> bool {
585        match *self {
586            Metadata => true,    // 0
587            ContactList => true, // 3
588            _ => {
589                let u: u32 = From::from(*self);
590                (10000..=19999).contains(&u) || (30000..=39999).contains(&u)
591            }
592        }
593    }
594
595    /// If this event kind is ephemeral
596    pub fn is_ephemeral(&self) -> bool {
597        let u: u32 = From::from(*self);
598        (20000..=29999).contains(&u)
599    }
600
601    /// If this event kind is parameterized replaceable
602    pub fn is_parameterized_replaceable(&self) -> bool {
603        let u: u32 = From::from(*self);
604        (30000..=39999).contains(&u)
605    }
606
607    /// If this event kind is addressable (new name for parameterized replaceable)
608    #[inline]
609    pub fn is_addressable(&self) -> bool {
610        self.is_parameterized_replaceable()
611    }
612
613    /// If this event kind is feed related.
614    pub fn is_feed_related(&self) -> bool {
615        self.is_feed_displayable() || self.augments_feed_related()
616    }
617
618    /// If this event kind is feed displayable.
619    pub fn is_feed_displayable(&self) -> bool {
620        matches!(
621            *self,
622            TextNote
623                | GroupChatMessage
624                | GroupChatThreadedReply
625                | GroupChatThread
626                | GroupChatReply
627                | EncryptedDirectMessage
628                | Repost
629                | DmChat
630                | GenericRepost
631                | Picture
632                | ChannelMessage
633                | FileMetadata
634                | Comment
635                | LiveChatMessage
636                | Patches
637                | GitIssue
638                | GitReply
639                | GitStatusOpen
640                | GitStatusApplied
641                | GitStatusClosed
642                | GitStatusDraft
643                | LongFormContent
644                | DraftLongFormContent
645        )
646    }
647
648    /// If this event kind's contents are textual (and thus may have links, etc)
649    pub fn is_textual(&self) -> bool {
650        matches!(
651            *self,
652            TextNote
653                | GroupChatMessage
654                | GroupChatThreadedReply
655                | GroupChatThread
656                | GroupChatReply
657            // NOT EncryptedDirectMesasge
658            // NOT Repost
659            // NOT DmChat
660            // NOT GenericRepost
661            // NOT Picture
662                | ChannelMessage
663                | Comment
664                | LiveChatMessage // NOT Patches (is a diff)
665                                  // NOT GitIssue (is markdown)
666                                  // NOT GitReply (is markdown)
667                                  // NOT GitStatusX (is markdown)
668                                  // NOT LongFormContent (is markdown)
669                                  // NOT DraftLongFormContent (is markdown)
670        )
671    }
672
673    /// Is direct message related
674    pub fn is_direct_message_related(&self) -> bool {
675        matches!(*self, EncryptedDirectMessage | DmChat | GiftWrap)
676    }
677
678    /// If this event kind augments a feed related event
679    pub fn augments_feed_related(&self) -> bool {
680        matches!(
681            *self,
682            EventDeletion | Reaction | Timestamp | Label | Reporting | Zap
683        )
684    }
685
686    /// If the contents are expected to be encrypted (or empty)
687    pub fn contents_are_encrypted(&self) -> bool {
688        matches!(
689            *self,
690            EncryptedDirectMessage
691                | MuteList
692                | PinList
693                | BookmarkList
694                | CommunityList
695                | PublicChatsList
696                | BlockedRelaysList
697                | SearchRelaysList
698                | InterestsList
699                | UserEmojiList
700                | JobRequest(_)
701                | JobResult(_)
702                | WalletRequest
703                | WalletResponse
704                | NostrConnect
705        )
706    }
707
708    /// This iterates through every well-known EventKind
709    pub fn iter() -> EventKindIterator {
710        EventKindIterator::new()
711    }
712}
713
714/// Iterator over well known `EventKind`s
715#[derive(Clone, Copy, Debug)]
716pub struct EventKindIterator {
717    pos: usize,
718}
719
720impl EventKindIterator {
721    fn new() -> EventKindIterator {
722        EventKindIterator { pos: 0 }
723    }
724}
725
726impl Iterator for EventKindIterator {
727    type Item = EventKind;
728
729    fn next(&mut self) -> Option<EventKind> {
730        if self.pos == WELL_KNOWN_KINDS.len() {
731            None
732        } else {
733            let rval = WELL_KNOWN_KINDS[self.pos];
734            self.pos += 1;
735            Some(rval)
736        }
737    }
738
739    fn size_hint(&self) -> (usize, Option<usize>) {
740        (self.pos, Some(WELL_KNOWN_KINDS.len()))
741    }
742}
743
744impl Serialize for EventKind {
745    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
746    where
747        S: Serializer,
748    {
749        let u: u32 = From::from(*self);
750        serializer.serialize_u32(u)
751    }
752}
753
754impl<'de> Deserialize<'de> for EventKind {
755    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
756    where
757        D: Deserializer<'de>,
758    {
759        deserializer.deserialize_u32(EventKindVisitor)
760    }
761}
762
763struct EventKindVisitor;
764
765impl Visitor<'_> for EventKindVisitor {
766    type Value = EventKind;
767
768    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
769        write!(f, "an unsigned number that matches a known EventKind")
770    }
771
772    fn visit_u32<E>(self, v: u32) -> Result<EventKind, E>
773    where
774        E: DeError,
775    {
776        Ok(From::<u32>::from(v))
777    }
778
779    // JsonValue numbers come in as u64
780    fn visit_u64<E>(self, v: u64) -> Result<EventKind, E>
781    where
782        E: DeError,
783    {
784        Ok(From::<u32>::from(v as u32))
785    }
786}
787
788#[cfg(feature = "speedy")]
789impl<'a, C: Context> Readable<'a, C> for EventKind {
790    #[inline]
791    fn read_from<R: Reader<'a, C>>(reader: &mut R) -> Result<Self, C::Error> {
792        let value = u32::read_from(reader)?;
793        Ok(value.into())
794    }
795
796    #[inline]
797    fn minimum_bytes_needed() -> usize {
798        <u32 as Readable<'a, C>>::minimum_bytes_needed()
799    }
800}
801
802#[cfg(feature = "speedy")]
803impl<C: Context> Writable<C> for EventKind {
804    #[inline]
805    fn write_to<T: ?Sized + Writer<C>>(&self, writer: &mut T) -> Result<(), C::Error> {
806        writer.write_u32(u32::from(*self))
807    }
808
809    #[inline]
810    fn bytes_needed(&self) -> Result<usize, C::Error> {
811        Ok(std::mem::size_of::<u32>())
812    }
813}
814
815/// Either an EventKind or a range (a vector of length 2 with start and end)
816#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
817#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
818#[serde(untagged)]
819pub enum EventKindOrRange {
820    /// A single EventKind
821    EventKind(EventKind),
822
823    /// A range of EventKinds
824    // NOTE: the internal Vec should have exactly 2 fields.  To force this with a tuple
825    //       struct makes ser/de a bitch, so we don't.
826    Range(Vec<EventKind>),
827}
828
829#[cfg(test)]
830mod test {
831    use super::*;
832
833    test_serde! {EventKind, test_event_kind_serde}
834
835    #[test]
836    fn test_replaceable_ephemeral() {
837        assert!(Metadata.is_replaceable());
838        assert!(!TextNote.is_replaceable());
839        assert!(!Zap.is_replaceable());
840        assert!(LongFormContent.is_replaceable());
841
842        assert!(!TextNote.is_ephemeral());
843        assert!(Auth.is_ephemeral());
844
845        assert!(!TextNote.is_parameterized_replaceable());
846        assert!(LongFormContent.is_parameterized_replaceable());
847    }
848
849    #[cfg(feature = "speedy")]
850    #[test]
851    fn test_speedy_event_kind() {
852        let ek = EventKind::mock();
853        let bytes = ek.write_to_vec().unwrap();
854        let ek2 = EventKind::read_from_buffer(&bytes).unwrap();
855        assert_eq!(ek, ek2);
856    }
857}