1use cfx_types::{H256, U64};
6use primitives::{
7 BlockHashOrEpochNumber as PrimitiveBlockHashOrEpochNumber,
8 EpochNumber as PrimitiveEpochNumber,
9};
10use serde::{
11 de::{Error, MapAccess, Visitor},
12 Deserialize, Deserializer, Serialize, Serializer,
13};
14use std::{fmt, str::FromStr};
15
16#[derive(Debug, PartialEq, Clone, Hash, Eq)]
18pub enum EpochNumber {
19 Num(U64),
21 Earliest,
23 LatestCheckpoint,
25 LatestFinalized,
27 LatestConfirmed,
29 LatestState,
31 LatestMined,
33}
34
35impl<'a> Deserialize<'a> for EpochNumber {
40 fn deserialize<D>(deserializer: D) -> Result<EpochNumber, D::Error>
41 where D: Deserializer<'a> {
42 deserializer.deserialize_any(EpochNumberVisitor)
43 }
44}
45
46impl Serialize for EpochNumber {
47 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
48 where S: Serializer {
49 match *self {
50 EpochNumber::Num(ref x) => {
51 serializer.serialize_str(&format!("0x{:x}", x))
52 }
53 EpochNumber::LatestMined => {
54 serializer.serialize_str("latest_mined")
55 }
56 EpochNumber::LatestFinalized => {
57 serializer.serialize_str("latest_finalized")
58 }
59 EpochNumber::LatestState => {
60 serializer.serialize_str("latest_state")
61 }
62 EpochNumber::Earliest => serializer.serialize_str("earliest"),
63 EpochNumber::LatestCheckpoint => {
64 serializer.serialize_str("latest_checkpoint")
65 }
66 EpochNumber::LatestConfirmed => {
67 serializer.serialize_str("latest_confirmed")
68 }
69 }
70 }
71}
72
73impl EpochNumber {
74 pub fn into_primitive(self) -> PrimitiveEpochNumber {
75 match self {
76 EpochNumber::Earliest => PrimitiveEpochNumber::Earliest,
77 EpochNumber::LatestMined => PrimitiveEpochNumber::LatestMined,
78 EpochNumber::LatestState => PrimitiveEpochNumber::LatestState,
79 EpochNumber::LatestFinalized => {
80 PrimitiveEpochNumber::LatestFinalized
81 }
82 EpochNumber::Num(num) => PrimitiveEpochNumber::Number(num.as_u64()),
83 EpochNumber::LatestCheckpoint => {
84 PrimitiveEpochNumber::LatestCheckpoint
85 }
86 EpochNumber::LatestConfirmed => {
87 PrimitiveEpochNumber::LatestConfirmed
88 }
89 }
90 }
91}
92
93impl FromStr for EpochNumber {
94 type Err = String;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 match s {
98 "latest_mined" => Ok(EpochNumber::LatestMined),
99 "latest_state" => Ok(EpochNumber::LatestState),
100 "latest_finalized" => Ok(EpochNumber::LatestFinalized),
101 "latest_confirmed" => Ok(EpochNumber::LatestConfirmed),
102 "earliest" => Ok(EpochNumber::Earliest),
103 "latest_checkpoint" => Ok(EpochNumber::LatestCheckpoint),
104 _ if s.starts_with("0x") => u64::from_str_radix(&s[2..], 16)
105 .map(U64::from)
106 .map(EpochNumber::Num)
107 .map_err(|e| format!("Invalid epoch number: {}", e)),
108 _ => Err("Invalid epoch number: missing 0x prefix".to_string()),
109 }
110 }
111}
112
113impl Into<PrimitiveEpochNumber> for EpochNumber {
114 fn into(self) -> PrimitiveEpochNumber { self.into_primitive() }
115}
116
117impl Into<EpochNumber> for u64 {
118 fn into(self) -> EpochNumber { EpochNumber::Num(U64::from(self)) }
119}
120
121struct EpochNumberVisitor;
122
123impl<'a> Visitor<'a> for EpochNumberVisitor {
124 type Value = EpochNumber;
125
126 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
127 write!(
128 formatter,
129 "an epoch number or 'latest_mined', 'latest_state', 'latest_checkpoint', 'latest_finalized', 'latest_confirmed' or 'earliest'"
130 )
131 }
132
133 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
134 where E: Error {
135 value.parse().map_err(Error::custom)
136 }
137
138 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
139 where E: Error {
140 self.visit_str(value.as_ref())
141 }
142}
143
144#[derive(Debug, PartialEq, Clone, Hash, Eq)]
145pub enum BlockHashOrEpochNumber {
146 BlockHashWithOption {
147 hash: H256,
148 require_pivot: Option<bool>,
151 },
152 EpochNumber(EpochNumber),
153}
154
155impl BlockHashOrEpochNumber {
156 pub fn into_primitive(self) -> PrimitiveBlockHashOrEpochNumber {
157 match self {
158 BlockHashOrEpochNumber::BlockHashWithOption {
159 hash,
160 require_pivot,
161 } => PrimitiveBlockHashOrEpochNumber::BlockHashWithOption {
162 hash,
163 require_pivot,
164 },
165 BlockHashOrEpochNumber::EpochNumber(epoch_number) => {
166 PrimitiveBlockHashOrEpochNumber::EpochNumber(
167 epoch_number.into(),
168 )
169 }
170 }
171 }
172}
173
174impl Into<PrimitiveBlockHashOrEpochNumber> for BlockHashOrEpochNumber {
175 fn into(self) -> PrimitiveBlockHashOrEpochNumber { self.into_primitive() }
176}
177
178impl<'a> Deserialize<'a> for BlockHashOrEpochNumber {
179 fn deserialize<D>(
180 deserializer: D,
181 ) -> Result<BlockHashOrEpochNumber, D::Error>
182 where D: Deserializer<'a> {
183 deserializer.deserialize_any(BlockHashOrEpochNumberVisitor)
184 }
185}
186
187impl Serialize for BlockHashOrEpochNumber {
188 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189 where S: Serializer {
190 match self {
191 BlockHashOrEpochNumber::EpochNumber(epoch_number) => {
192 epoch_number.serialize(serializer)
193 }
194 BlockHashOrEpochNumber::BlockHashWithOption {
195 hash,
196 require_pivot,
197 } => {
198 if let Some(require_pivot) = require_pivot {
201 serializer.serialize_str(&format!(
202 "{{ 'hash': '{}', 'requirePivot': '{}' }}",
203 hash, require_pivot
204 ))
205 } else {
206 serializer.serialize_str(&format!("hash:{:#x}", hash))
207 }
208 }
209 }
210 }
211}
212
213struct BlockHashOrEpochNumberVisitor;
214
215impl<'a> Visitor<'a> for BlockHashOrEpochNumberVisitor {
221 type Value = BlockHashOrEpochNumber;
222
223 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
224 write!(
225 formatter,
226 "an epoch number or 'latest_mined', 'latest_state', 'latest_checkpoint', 'latest_finalized', \
227 'latest_confirmed', or 'earliest', or 'hash:<BLOCK_HASH>'"
228 )
229 }
230
231 fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
232 where V: MapAccess<'a> {
233 let (mut require_pivot, mut epoch_number, mut block_hash) =
235 (true, None::<u64>, None::<H256>);
236
237 loop {
239 let key_str: Option<String> = visitor.next_key()?;
240
241 match key_str {
242 Some(key) => match key.as_str() {
243 "epochNumber" => {
244 let value: String = visitor.next_value()?;
245 if value.starts_with("0x") {
246 let number = u64::from_str_radix(&value[2..], 16)
247 .map_err(|e| {
248 Error::custom(format!(
249 "Invalid epoch number: {}",
250 e
251 ))
252 })?;
253
254 epoch_number = Some(number.into());
255 break;
256 } else {
257 return Err(Error::custom(
258 "Invalid block number: missing 0x prefix"
259 .to_string(),
260 ));
261 }
262 }
263 "blockHash" => {
264 block_hash = Some(visitor.next_value()?);
265 }
266 "requirePivot" => {
267 require_pivot = visitor.next_value()?;
268 }
269 key => {
270 return Err(Error::custom(format!(
271 "Unknown key: {}",
272 key
273 )))
274 }
275 },
276 None => break,
277 };
278 }
279
280 if let Some(number) = epoch_number {
281 return Ok(BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num(
282 number.into(),
283 )));
284 }
285
286 if let Some(hash) = block_hash {
287 return Ok(BlockHashOrEpochNumber::BlockHashWithOption {
288 hash,
289 require_pivot: Some(require_pivot),
290 });
291 }
292
293 return Err(Error::custom("Invalid input"));
294 }
295
296 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
297 where E: Error {
298 if value.starts_with("hash:0x") {
299 Ok(BlockHashOrEpochNumber::BlockHashWithOption {
300 hash: value[7..].parse().map_err(Error::custom)?,
301 require_pivot: None,
302 })
303 } else {
304 value.parse().map_err(Error::custom).map(
305 |epoch_number: EpochNumber| {
306 BlockHashOrEpochNumber::EpochNumber(epoch_number)
307 },
308 )
309 }
310 }
311
312 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
313 where E: Error {
314 self.visit_str(value.as_ref())
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use serde_json;
322 use std::str::FromStr;
323
324 #[test]
325 fn block_hash_or_epoch_number_deserialization() {
326 let s = r#"[
327 "0xa",
328 "latest_state",
329 "earliest",
330 "latest_mined",
331 "latest_checkpoint",
332 "latest_confirmed",
333 "latest_finalized",
334 "hash:0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
335 {"epochNumber": "0xa"},
336 {"blockHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"},
337 {"blockHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "requirePivot": false}
338 ]"#;
339 let deserialized: Vec<BlockHashOrEpochNumber> =
340 serde_json::from_str(s).unwrap();
341
342 assert_eq!(
343 deserialized,
344 vec![
345 BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num((10).into())),
346 BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestState),
347 BlockHashOrEpochNumber::EpochNumber(EpochNumber::Earliest),
348 BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestMined),
349 BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestCheckpoint),
350 BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestConfirmed),
351 BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestFinalized),
352 BlockHashOrEpochNumber::BlockHashWithOption {
353 hash: H256::from_str(
354 "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
355 )
356 .unwrap(),
357 require_pivot: None
360 },
361 BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num((10).into())),
362 BlockHashOrEpochNumber::BlockHashWithOption {
363 hash: H256::from_str(
364 "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
365 )
366 .unwrap(),
367 require_pivot: Some(true)
368 },
369 BlockHashOrEpochNumber::BlockHashWithOption {
370 hash: H256::from_str(
371 "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
372 )
373 .unwrap(),
374 require_pivot: Some(false)
375 }
376 ]
377 )
378 }
379
380 #[test]
381 fn should_not_deserialize() {
382 let s = r#"[{}, "10"]"#;
383 assert!(serde_json::from_str::<Vec<BlockHashOrEpochNumber>>(s).is_err());
384 }
385}