@@ -14,6 +14,7 @@ use core::cmp::Ordering;
14
14
use core:: fmt;
15
15
use core:: hash:: { Hash , Hasher } ;
16
16
use core:: ops:: Deref ;
17
+ use core:: str:: FromStr ;
17
18
18
19
use bitcoin:: secp256k1:: schnorr:: Signature ;
19
20
use bitcoin:: secp256k1:: { self , Message , Secp256k1 , Verification } ;
@@ -48,13 +49,52 @@ use crate::{Alphabet, JsonUtil, PublicKey, SingleLetterTag, Timestamp};
48
49
/// Tags Indexes
49
50
pub type TagsIndexes = BTreeMap < SingleLetterTag , BTreeSet < String > > ;
50
51
52
+ const ID : & str = "id" ;
53
+ const PUBKEY : & str = "pubkey" ;
54
+ const CREATED_AT : & str = "created_at" ;
55
+ const KIND : & str = "kind" ;
56
+ const TAGS : & str = "tags" ;
57
+ const CONTENT : & str = "content" ;
58
+ const SIG : & str = "sig" ;
59
+
60
+ /// Supported event keys
61
+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord , Hash ) ]
62
+ enum EventKey {
63
+ Id ,
64
+ PubKey ,
65
+ CreatedAt ,
66
+ Kind ,
67
+ Tags ,
68
+ Content ,
69
+ Sig ,
70
+ }
71
+
72
+ impl FromStr for EventKey {
73
+ type Err = Error ;
74
+
75
+ fn from_str ( key : & str ) -> Result < Self , Self :: Err > {
76
+ match key {
77
+ ID => Ok ( Self :: Id ) ,
78
+ PUBKEY => Ok ( Self :: PubKey ) ,
79
+ CREATED_AT => Ok ( Self :: CreatedAt ) ,
80
+ KIND => Ok ( Self :: Kind ) ,
81
+ TAGS => Ok ( Self :: Tags ) ,
82
+ CONTENT => Ok ( Self :: Content ) ,
83
+ SIG => Ok ( Self :: Sig ) ,
84
+ k => Err ( Error :: UnknownKey ( k. to_string ( ) ) ) ,
85
+ }
86
+ }
87
+ }
88
+
51
89
/// [`Event`] error
52
90
#[ derive( Debug , PartialEq , Eq ) ]
53
91
pub enum Error {
54
92
/// Invalid signature
55
93
InvalidSignature ,
56
94
/// Invalid event id
57
95
InvalidId ,
96
+ /// Unknown JSON event key
97
+ UnknownKey ( String ) ,
58
98
/// Error serializing or deserializing JSON data
59
99
Json ( String ) ,
60
100
/// Secp256k1 error
@@ -69,6 +109,7 @@ impl fmt::Display for Error {
69
109
match self {
70
110
Self :: InvalidSignature => write ! ( f, "Invalid signature" ) ,
71
111
Self :: InvalidId => write ! ( f, "Invalid event id" ) ,
112
+ Self :: UnknownKey ( key) => write ! ( f, "Unknown JSON event key: {key}" ) ,
72
113
Self :: Json ( e) => write ! ( f, "Json: {e}" ) ,
73
114
Self :: Secp256k1 ( e) => write ! ( f, "Secp256k1: {e}" ) ,
74
115
}
@@ -93,7 +134,7 @@ pub struct Event {
93
134
/// Event
94
135
inner : EventIntermediate ,
95
136
/// JSON deserialization key order
96
- deser_order : Vec < String > ,
137
+ deser_order : Vec < EventKey > ,
97
138
/// Tags indexes
98
139
#[ cfg( feature = "std" ) ]
99
140
tags_indexes : Arc < OnceCell < TagsIndexes > > ,
@@ -102,13 +143,13 @@ pub struct Event {
102
143
impl fmt:: Debug for Event {
103
144
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
104
145
f. debug_struct ( "Event" )
105
- . field ( "id" , & self . inner . id )
106
- . field ( "pubkey" , & self . inner . pubkey )
107
- . field ( "created_at" , & self . inner . created_at )
108
- . field ( "kind" , & self . inner . kind )
109
- . field ( "tags" , & self . inner . tags )
110
- . field ( "content" , & self . inner . content )
111
- . field ( "sig" , & self . inner . sig )
146
+ . field ( ID , & self . inner . id )
147
+ . field ( PUBKEY , & self . inner . pubkey )
148
+ . field ( CREATED_AT , & self . inner . created_at )
149
+ . field ( KIND , & self . inner . kind )
150
+ . field ( TAGS , & self . inner . tags )
151
+ . field ( CONTENT , & self . inner . content )
152
+ . field ( SIG , & self . inner . sig )
112
153
. finish ( )
113
154
}
114
155
}
@@ -531,15 +572,14 @@ impl Serialize for Event {
531
572
} else {
532
573
let mut s = serializer. serialize_struct ( "Event" , 7 ) ?;
533
574
for key in self . deser_order . iter ( ) {
534
- match key. as_str ( ) {
535
- "id" => s. serialize_field ( "id" , & self . inner . id ) ?,
536
- "pubkey" => s. serialize_field ( "pubkey" , & self . inner . pubkey ) ?,
537
- "created_at" => s. serialize_field ( "created_at" , & self . inner . created_at ) ?,
538
- "kind" => s. serialize_field ( "kind" , & self . inner . kind ) ?,
539
- "tags" => s. serialize_field ( "tags" , & self . inner . tags ) ?,
540
- "content" => s. serialize_field ( "content" , & self . inner . content ) ?,
541
- "sig" => s. serialize_field ( "sig" , & self . inner . sig ) ?,
542
- _ => return Err ( serde:: ser:: Error :: custom ( format ! ( "Unknown key: {}" , key) ) ) ,
575
+ match key {
576
+ EventKey :: Id => s. serialize_field ( ID , & self . inner . id ) ?,
577
+ EventKey :: PubKey => s. serialize_field ( PUBKEY , & self . inner . pubkey ) ?,
578
+ EventKey :: CreatedAt => s. serialize_field ( CREATED_AT , & self . inner . created_at ) ?,
579
+ EventKey :: Kind => s. serialize_field ( KIND , & self . inner . kind ) ?,
580
+ EventKey :: Tags => s. serialize_field ( TAGS , & self . inner . tags ) ?,
581
+ EventKey :: Content => s. serialize_field ( CONTENT , & self . inner . content ) ?,
582
+ EventKey :: Sig => s. serialize_field ( SIG , & self . inner . sig ) ?,
543
583
}
544
584
}
545
585
s. end ( )
@@ -554,10 +594,13 @@ impl<'de> Deserialize<'de> for Event {
554
594
{
555
595
let value: Value = Value :: deserialize ( deserializer) ?;
556
596
557
- let mut deser_order: Vec < String > = Vec :: with_capacity ( 7 ) ;
558
- if let Value :: Object ( map) = & value {
559
- deser_order = map. keys ( ) . cloned ( ) . collect ( ) ;
560
- }
597
+ let deser_order: Vec < EventKey > = if let Value :: Object ( map) = & value {
598
+ map. keys ( )
599
+ . filter_map ( |k| EventKey :: from_str ( k) . ok ( ) )
600
+ . collect ( )
601
+ } else {
602
+ Vec :: new ( )
603
+ } ;
561
604
562
605
Ok ( Self {
563
606
inner : serde_json:: from_value ( value) . map_err ( serde:: de:: Error :: custom) ?,
@@ -661,13 +704,13 @@ mod tests {
661
704
assert_eq ! (
662
705
event. deser_order,
663
706
vec![
664
- "kind" ,
665
- "pubkey" ,
666
- "content" ,
667
- "created_at" ,
668
- "id" ,
669
- "sig" ,
670
- "tags"
707
+ EventKey :: Kind ,
708
+ EventKey :: PubKey ,
709
+ EventKey :: Content ,
710
+ EventKey :: CreatedAt ,
711
+ EventKey :: Id ,
712
+ EventKey :: Sig ,
713
+ EventKey :: Tags
671
714
]
672
715
) ;
673
716
assert_eq ! ( json, reserialized_json) ;
@@ -677,6 +720,45 @@ mod tests {
677
720
let reserialized_json = event. as_json ( ) ;
678
721
assert_eq ! ( json, reserialized_json) ;
679
722
}
723
+
724
+ #[ test]
725
+ fn test_event_with_unknown_fields ( ) {
726
+ let json: & str = r##"{
727
+ "citedNotesCache": [],
728
+ "citedUsersCache": [
729
+ "aac07d95089ce6adf08b9156d43c1a4ab594c6130b7dcb12ec199008c5819a2f"
730
+ ],
731
+ "content": "#JoininBox is a minimalistic, security focused Linux environment for #JoinMarket with a terminal based graphical menu.\n\nnostr:npub14tq8m9ggnnn2muytj9tdg0q6f26ef3snpd7ukyhvrxgq33vpnghs8shy62 👍🧡\n\nhttps://www.nobsbitcoin.com/joininbox-v0-8-0/",
732
+ "created_at": 1687070234,
733
+ "id": "c8acc12a232ea6caedfaaf0c52148635de6ffd312c3f432c6eca11720c102e54",
734
+ "kind": 1,
735
+ "pubkey": "27154fb873badf69c3ea83a0da6e65d6a150d2bf8f7320fc3314248d74645c64",
736
+ "sig": "e27062b1b7187ffa0b521dab23fff6c6b62c00fd1b029e28368d7d070dfb225f7e598e3b1c6b1e2335b286ec3702492bce152035105b934f594cd7323d84f0ee",
737
+ "tags": [
738
+ [
739
+ "t",
740
+ "joininbox"
741
+ ],
742
+ [
743
+ "t",
744
+ "joinmarket"
745
+ ],
746
+ [
747
+ "p",
748
+ "aac07d95089ce6adf08b9156d43c1a4ab594c6130b7dcb12ec199008c5819a2f"
749
+ ]
750
+ ]
751
+ }"## ;
752
+
753
+ // Deserialize
754
+ let event = Event :: from_json ( json) . unwrap ( ) ;
755
+
756
+ // Re-serialize
757
+ let re_serialized_json = event. as_json ( ) ;
758
+
759
+ let expected_json: & str = r##"{"content":"#JoininBox is a minimalistic, security focused Linux environment for #JoinMarket with a terminal based graphical menu.\n\nnostr:npub14tq8m9ggnnn2muytj9tdg0q6f26ef3snpd7ukyhvrxgq33vpnghs8shy62 👍🧡\n\nhttps://www.nobsbitcoin.com/joininbox-v0-8-0/","created_at":1687070234,"id":"c8acc12a232ea6caedfaaf0c52148635de6ffd312c3f432c6eca11720c102e54","kind":1,"pubkey":"27154fb873badf69c3ea83a0da6e65d6a150d2bf8f7320fc3314248d74645c64","sig":"e27062b1b7187ffa0b521dab23fff6c6b62c00fd1b029e28368d7d070dfb225f7e598e3b1c6b1e2335b286ec3702492bce152035105b934f594cd7323d84f0ee","tags":[["t","joininbox"],["t","joinmarket"],["p","aac07d95089ce6adf08b9156d43c1a4ab594c6130b7dcb12ec199008c5819a2f"]]}"## ;
760
+ assert_eq ! ( re_serialized_json, expected_json. trim( ) ) ;
761
+ }
680
762
}
681
763
682
764
#[ cfg( bench) ]
0 commit comments