1
- use std:: collections:: HashMap ;
1
+ use std:: { collections:: HashMap , fmt } ;
2
2
3
3
use apache_avro:: {
4
4
schema:: { derive:: AvroSchemaComponent , Name } ,
5
5
AvroSchema ,
6
6
Schema ,
7
7
} ;
8
8
use chrono:: { DateTime , TimeZone , Utc } ;
9
- use serde:: { Deserialize , Serialize } ;
9
+ use serde:: {
10
+ de:: { self , Deserializer , Visitor } ,
11
+ ser:: Serializer ,
12
+ Deserialize ,
13
+ Serialize ,
14
+ } ;
10
15
use wrapped_int:: WrappedU32 ;
11
16
12
17
use crate :: * ;
13
18
14
- #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
19
+ #[ derive( Debug , Clone , PartialEq ) ]
15
20
pub struct BlockTime ( pub FuelCoreTai64 ) ;
21
+
16
22
impl BlockTime {
17
23
pub fn into_inner ( self ) -> FuelCoreTai64 {
18
24
self . 0
@@ -34,6 +40,87 @@ impl Default for BlockTime {
34
40
}
35
41
}
36
42
43
+ impl Serialize for BlockTime {
44
+ fn serialize < S > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error >
45
+ where
46
+ S : Serializer ,
47
+ {
48
+ let unix_timestamp = self . 0 . to_unix ( ) ;
49
+ serializer. serialize_i64 ( unix_timestamp)
50
+ }
51
+ }
52
+
53
+ impl < ' de > Deserialize < ' de > for BlockTime {
54
+ fn deserialize < D > ( deserializer : D ) -> Result < Self , D :: Error >
55
+ where
56
+ D : Deserializer < ' de > ,
57
+ {
58
+ struct BlockTimeVisitor ;
59
+
60
+ impl < ' de > Visitor < ' de > for BlockTimeVisitor {
61
+ type Value = BlockTime ;
62
+
63
+ fn expecting ( & self , formatter : & mut fmt:: Formatter ) -> fmt:: Result {
64
+ formatter. write_str (
65
+ "a string Unix timestamp or an 8-byte array or an integer" ,
66
+ )
67
+ }
68
+
69
+ fn visit_i64 < E > ( self , value : i64 ) -> Result < Self :: Value , E >
70
+ where
71
+ E : de:: Error ,
72
+ {
73
+ Ok ( BlockTime ( FuelCoreTai64 :: from_unix ( value) ) )
74
+ }
75
+
76
+ fn visit_u64 < E > ( self , value : u64 ) -> Result < Self :: Value , E >
77
+ where
78
+ E : de:: Error ,
79
+ {
80
+ Ok ( BlockTime ( FuelCoreTai64 :: from_unix ( value as i64 ) ) )
81
+ }
82
+
83
+ fn visit_str < E > ( self , value : & str ) -> Result < Self :: Value , E >
84
+ where
85
+ E : de:: Error ,
86
+ {
87
+ let unix_timestamp =
88
+ value. parse :: < i64 > ( ) . map_err ( de:: Error :: custom) ?;
89
+ Ok ( BlockTime ( FuelCoreTai64 :: from_unix ( unix_timestamp) ) )
90
+ }
91
+
92
+ fn visit_bytes < E > ( self , value : & [ u8 ] ) -> Result < Self :: Value , E >
93
+ where
94
+ E : de:: Error ,
95
+ {
96
+ let tai64 = FuelCoreTai64 :: from_slice ( value) . map_err ( |_| {
97
+ de:: Error :: custom ( "expected an 8-byte array for TAI64" )
98
+ } ) ?;
99
+ Ok ( BlockTime ( tai64) )
100
+ }
101
+
102
+ fn visit_seq < A > ( self , mut seq : A ) -> Result < Self :: Value , A :: Error >
103
+ where
104
+ A : de:: SeqAccess < ' de > ,
105
+ {
106
+ let mut bytes = [ 0u8 ; 8 ] ;
107
+ for byte in & mut bytes {
108
+ * byte = seq. next_element ( ) ?. ok_or_else ( || {
109
+ de:: Error :: custom ( "byte array too short" )
110
+ } ) ?;
111
+ }
112
+ if seq. next_element :: < u8 > ( ) ?. is_some ( ) {
113
+ return Err ( de:: Error :: custom ( "byte array too long" ) ) ;
114
+ }
115
+ let tai64 = FuelCoreTai64 :: from ( bytes) ;
116
+ Ok ( BlockTime ( tai64) )
117
+ }
118
+ }
119
+
120
+ deserializer. deserialize_any ( BlockTimeVisitor )
121
+ }
122
+ }
123
+
37
124
impl utoipa:: ToSchema for BlockTime {
38
125
fn name ( ) -> std:: borrow:: Cow < ' static , str > {
39
126
std:: borrow:: Cow :: Borrowed ( "BlockTime" )
@@ -63,12 +150,10 @@ impl AvroSchemaComponent for BlockTime {
63
150
_ctxt : & mut HashMap < Name , Schema > ,
64
151
_namespace : & Option < String > ,
65
152
) -> Schema {
66
- // Use Avro's `long` type (i64) for serialization
67
153
Schema :: Long
68
154
}
69
155
}
70
156
71
- // Header type
72
157
#[ derive(
73
158
Debug ,
74
159
Clone ,
@@ -135,7 +220,6 @@ impl From<&FuelCoreBlockHeader> for BlockHeader {
135
220
}
136
221
}
137
222
138
- // BlockHeaderVersion enum
139
223
#[ derive(
140
224
Debug ,
141
225
Clone ,
@@ -147,9 +231,90 @@ impl From<&FuelCoreBlockHeader> for BlockHeader {
147
231
derive_more:: Display ,
148
232
apache_avro:: AvroSchema ,
149
233
) ]
150
- #[ serde( rename_all = "SCREAMING_SNAKE_CASE " ) ]
234
+ #[ serde( rename_all = "snake_case " ) ]
151
235
pub enum BlockHeaderVersion {
152
236
#[ default]
153
237
#[ display( "V1" ) ]
154
238
V1 ,
155
239
}
240
+
241
+ #[ cfg( test) ]
242
+ mod tests {
243
+ use pretty_assertions:: assert_eq;
244
+ use serde_json;
245
+
246
+ use super :: * ;
247
+
248
+ #[ test]
249
+ fn test_serialize_string ( ) {
250
+ let block_time = BlockTime :: from_unix ( 1614556800 ) ;
251
+ let serialized = serde_json:: to_string ( & block_time) . unwrap ( ) ;
252
+ assert_eq ! ( serialized, "1614556800" ) ; // Expect integer
253
+ }
254
+
255
+ #[ test]
256
+ fn test_deserialize_string ( ) {
257
+ let json = "\" 1614556800\" " ;
258
+ let deserialized: BlockTime = serde_json:: from_str ( json) . unwrap ( ) ;
259
+ let expected = BlockTime :: from_unix ( 1614556800 ) ;
260
+ assert_eq ! ( deserialized, expected) ;
261
+ assert_eq ! ( deserialized. 0 . to_unix( ) , 1614556800 ) ;
262
+ }
263
+
264
+ #[ test]
265
+ fn test_deserialize_byte_array ( ) {
266
+ let block_time = BlockTime :: from_unix ( 1614556800 ) ;
267
+ let bytes = block_time. 0 . to_bytes ( ) ;
268
+ let json = serde_json:: to_string ( & bytes. to_vec ( ) ) . unwrap ( ) ;
269
+ let deserialized: BlockTime = serde_json:: from_str ( & json) . unwrap ( ) ;
270
+ assert_eq ! ( deserialized, block_time) ;
271
+ assert_eq ! ( deserialized. 0 . to_unix( ) , 1614556800 ) ;
272
+ }
273
+
274
+ #[ test]
275
+ fn test_round_trip_string ( ) {
276
+ let original = BlockTime :: from_unix ( 1614556800 ) ;
277
+ let serialized = serde_json:: to_string ( & original) . unwrap ( ) ;
278
+ let deserialized: BlockTime =
279
+ serde_json:: from_str ( & serialized) . unwrap ( ) ;
280
+ assert_eq ! ( deserialized, original) ;
281
+ }
282
+
283
+ #[ test]
284
+ fn test_deserialize_invalid_string ( ) {
285
+ let json = "\" not-a-number\" " ;
286
+ let result = serde_json:: from_str :: < BlockTime > ( json) ;
287
+ assert ! ( result. is_err( ) ) ;
288
+ }
289
+
290
+ #[ test]
291
+ fn test_deserialize_invalid_byte_array_length ( ) {
292
+ let json = "[1, 2, 3]" ;
293
+ let result = serde_json:: from_str :: < BlockTime > ( json) ;
294
+ assert ! ( result. is_err( ) ) ;
295
+
296
+ let json = "[1, 2, 3, 4, 5, 6, 7, 8, 9]" ;
297
+ let result = serde_json:: from_str :: < BlockTime > ( json) ;
298
+ assert ! ( result. is_err( ) ) ;
299
+ }
300
+
301
+ #[ test]
302
+ fn test_get_timestamp_utc ( ) {
303
+ let block_time = BlockTime :: from_unix ( 1614556800 ) ;
304
+ let header = BlockHeader {
305
+ time : block_time,
306
+ ..Default :: default ( )
307
+ } ;
308
+ let utc_time = header. get_timestamp_utc ( ) ;
309
+ assert_eq ! ( utc_time. to_rfc3339( ) , "2021-03-01T00:00:00+00:00" ) ;
310
+ }
311
+
312
+ #[ test]
313
+ fn test_deserialize_integer ( ) {
314
+ let json = "1614556800" ;
315
+ let deserialized: BlockTime = serde_json:: from_str ( json) . unwrap ( ) ;
316
+ let expected = BlockTime :: from_unix ( 1614556800 ) ;
317
+ assert_eq ! ( deserialized, expected) ;
318
+ assert_eq ! ( deserialized. 0 . to_unix( ) , 1614556800 ) ;
319
+ }
320
+ }
0 commit comments