commit 11a552fb6be730588bd2599d2e44547df6bdbeb7
parent 880e2fe9250d1bb96b3f0e6c19721797fb06c8c1
Author: triesap <tyson@radroots.org>
Date: Fri, 5 Jun 2026 20:32:31 -0700
protocol: add event kind classification
Diffstat:
1 file changed, 72 insertions(+), 6 deletions(-)
diff --git a/crates/tangle_protocol/src/lib.rs b/crates/tangle_protocol/src/lib.rs
@@ -167,6 +167,39 @@ impl Kind {
pub fn as_u32(self) -> u32 {
self.0
}
+
+ pub fn class(self) -> KindClass {
+ match self.0 {
+ 0 | 3 | 10_000..=19_999 => KindClass::Replaceable,
+ 20_000..=29_999 => KindClass::Ephemeral,
+ 30_000..=39_999 => KindClass::Addressable,
+ _ => KindClass::Regular,
+ }
+ }
+
+ pub fn is_regular(self) -> bool {
+ self.class() == KindClass::Regular
+ }
+
+ pub fn is_replaceable(self) -> bool {
+ self.class() == KindClass::Replaceable
+ }
+
+ pub fn is_ephemeral(self) -> bool {
+ self.class() == KindClass::Ephemeral
+ }
+
+ pub fn is_addressable(self) -> bool {
+ self.class() == KindClass::Addressable
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum KindClass {
+ Regular,
+ Replaceable,
+ Ephemeral,
+ Addressable,
}
impl fmt::Display for Kind {
@@ -961,12 +994,12 @@ fn kind_out_of_range_error(value: u64) -> String {
#[cfg(test)]
mod tests {
use super::{
- ClientMessage, Event, EventId, EventShapeError, Filter, Kind, PublicKeyHex, RawEventJson,
- RelayMessage, SignatureHex, SubscriptionId, Tag, TagName, TagValue, UnixTimestamp,
- UnsignedEvent, canonical_event_json, empty_error, encode_relay_message, event_from_value,
- event_to_value, filter_from_value, invalid_length_error, kind_out_of_range_error,
- non_lowercase_hex_error, parse_client_message, parse_event_json, relay_message_to_value,
- too_long_error,
+ ClientMessage, Event, EventId, EventShapeError, Filter, Kind, KindClass, PublicKeyHex,
+ RawEventJson, RelayMessage, SignatureHex, SubscriptionId, Tag, TagName, TagValue,
+ UnixTimestamp, UnsignedEvent, canonical_event_json, empty_error, encode_relay_message,
+ event_from_value, event_to_value, filter_from_value, invalid_length_error,
+ kind_out_of_range_error, non_lowercase_hex_error, parse_client_message, parse_event_json,
+ relay_message_to_value, too_long_error,
};
use core::str::FromStr;
use std::collections::hash_map::DefaultHasher;
@@ -1077,6 +1110,39 @@ mod tests {
}
#[test]
+ fn event_kind_classification_matches_nip01_ranges() {
+ for value in [1, 2, 4, 44, 1_000, 9_999, 45, 40_000] {
+ let kind = Kind::new(value).expect("regular");
+ assert_eq!(kind.class(), KindClass::Regular);
+ assert!(kind.is_regular());
+ assert!(!kind.is_replaceable());
+ assert!(!kind.is_ephemeral());
+ assert!(!kind.is_addressable());
+ }
+
+ for value in [0, 3, 10_000, 19_999] {
+ let kind = Kind::new(value).expect("replaceable");
+ assert_eq!(kind.class(), KindClass::Replaceable);
+ assert!(kind.is_replaceable());
+ }
+
+ for value in [20_000, 29_999] {
+ let kind = Kind::new(value).expect("ephemeral");
+ assert_eq!(kind.class(), KindClass::Ephemeral);
+ assert!(kind.is_ephemeral());
+ }
+
+ for value in [30_000, 39_999] {
+ let kind = Kind::new(value).expect("addressable");
+ assert_eq!(kind.class(), KindClass::Addressable);
+ assert!(kind.is_addressable());
+ }
+
+ assert_eq!(format!("{:?}", KindClass::Regular), "Regular");
+ assert_eq!(KindClass::Regular, KindClass::Regular);
+ }
+
+ #[test]
fn scalar_errors_have_stable_messages() {
assert_eq!(empty_error("id"), "id must not be empty");
assert_eq!(