use cfx_types::{H256, U64};
use primitives::{
BlockHashOrEpochNumber as PrimitiveBlockHashOrEpochNumber,
EpochNumber as PrimitiveEpochNumber,
};
use serde::{
de::{Error, MapAccess, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{fmt, str::FromStr};
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
pub enum EpochNumber {
Num(U64),
Earliest,
LatestCheckpoint,
LatestFinalized,
LatestConfirmed,
LatestState,
LatestMined,
}
impl<'a> Deserialize<'a> for EpochNumber {
fn deserialize<D>(deserializer: D) -> Result<EpochNumber, D::Error>
where D: Deserializer<'a> {
deserializer.deserialize_any(EpochNumberVisitor)
}
}
impl Serialize for EpochNumber {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
match *self {
EpochNumber::Num(ref x) => {
serializer.serialize_str(&format!("0x{:x}", x))
}
EpochNumber::LatestMined => {
serializer.serialize_str("latest_mined")
}
EpochNumber::LatestFinalized => {
serializer.serialize_str("latest_finalized")
}
EpochNumber::LatestState => {
serializer.serialize_str("latest_state")
}
EpochNumber::Earliest => serializer.serialize_str("earliest"),
EpochNumber::LatestCheckpoint => {
serializer.serialize_str("latest_checkpoint")
}
EpochNumber::LatestConfirmed => {
serializer.serialize_str("latest_confirmed")
}
}
}
}
impl EpochNumber {
pub fn into_primitive(self) -> PrimitiveEpochNumber {
match self {
EpochNumber::Earliest => PrimitiveEpochNumber::Earliest,
EpochNumber::LatestMined => PrimitiveEpochNumber::LatestMined,
EpochNumber::LatestState => PrimitiveEpochNumber::LatestState,
EpochNumber::LatestFinalized => {
PrimitiveEpochNumber::LatestFinalized
}
EpochNumber::Num(num) => PrimitiveEpochNumber::Number(num.as_u64()),
EpochNumber::LatestCheckpoint => {
PrimitiveEpochNumber::LatestCheckpoint
}
EpochNumber::LatestConfirmed => {
PrimitiveEpochNumber::LatestConfirmed
}
}
}
}
impl FromStr for EpochNumber {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"latest_mined" => Ok(EpochNumber::LatestMined),
"latest_state" => Ok(EpochNumber::LatestState),
"latest_finalized" => Ok(EpochNumber::LatestFinalized),
"latest_confirmed" => Ok(EpochNumber::LatestConfirmed),
"earliest" => Ok(EpochNumber::Earliest),
"latest_checkpoint" => Ok(EpochNumber::LatestCheckpoint),
_ if s.starts_with("0x") => u64::from_str_radix(&s[2..], 16)
.map(U64::from)
.map(EpochNumber::Num)
.map_err(|e| format!("Invalid epoch number: {}", e)),
_ => Err("Invalid epoch number: missing 0x prefix".to_string()),
}
}
}
impl Into<PrimitiveEpochNumber> for EpochNumber {
fn into(self) -> PrimitiveEpochNumber { self.into_primitive() }
}
impl Into<EpochNumber> for u64 {
fn into(self) -> EpochNumber { EpochNumber::Num(U64::from(self)) }
}
struct EpochNumberVisitor;
impl<'a> Visitor<'a> for EpochNumberVisitor {
type Value = EpochNumber;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(
formatter,
"an epoch number or 'latest_mined', 'latest_state', 'latest_checkpoint', 'latest_finalized', 'latest_confirmed' or 'earliest'"
)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where E: Error {
value.parse().map_err(Error::custom)
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where E: Error {
self.visit_str(value.as_ref())
}
}
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
pub enum BlockHashOrEpochNumber {
BlockHashWithOption {
hash: H256,
require_pivot: Option<bool>,
},
EpochNumber(EpochNumber),
}
impl BlockHashOrEpochNumber {
pub fn into_primitive(self) -> PrimitiveBlockHashOrEpochNumber {
match self {
BlockHashOrEpochNumber::BlockHashWithOption {
hash,
require_pivot,
} => PrimitiveBlockHashOrEpochNumber::BlockHashWithOption {
hash,
require_pivot,
},
BlockHashOrEpochNumber::EpochNumber(epoch_number) => {
PrimitiveBlockHashOrEpochNumber::EpochNumber(
epoch_number.into(),
)
}
}
}
}
impl Into<PrimitiveBlockHashOrEpochNumber> for BlockHashOrEpochNumber {
fn into(self) -> PrimitiveBlockHashOrEpochNumber { self.into_primitive() }
}
impl<'a> Deserialize<'a> for BlockHashOrEpochNumber {
fn deserialize<D>(
deserializer: D,
) -> Result<BlockHashOrEpochNumber, D::Error>
where D: Deserializer<'a> {
deserializer.deserialize_any(BlockHashOrEpochNumberVisitor)
}
}
impl Serialize for BlockHashOrEpochNumber {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
match self {
BlockHashOrEpochNumber::EpochNumber(epoch_number) => {
epoch_number.serialize(serializer)
}
BlockHashOrEpochNumber::BlockHashWithOption {
hash,
require_pivot,
} => {
if let Some(require_pivot) = require_pivot {
serializer.serialize_str(&format!(
"{{ 'hash': '{}', 'requirePivot': '{}' }}",
hash, require_pivot
))
} else {
serializer.serialize_str(&format!("hash:{:#x}", hash))
}
}
}
}
}
struct BlockHashOrEpochNumberVisitor;
impl<'a> Visitor<'a> for BlockHashOrEpochNumberVisitor {
type Value = BlockHashOrEpochNumber;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(
formatter,
"an epoch number or 'latest_mined', 'latest_state', 'latest_checkpoint', 'latest_finalized', \
'latest_confirmed', or 'earliest', or 'hash:<BLOCK_HASH>'"
)
}
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
where V: MapAccess<'a> {
let (mut require_pivot, mut epoch_number, mut block_hash) =
(true, None::<u64>, None::<H256>);
loop {
let key_str: Option<String> = visitor.next_key()?;
match key_str {
Some(key) => match key.as_str() {
"epochNumber" => {
let value: String = visitor.next_value()?;
if value.starts_with("0x") {
let number = u64::from_str_radix(&value[2..], 16)
.map_err(|e| {
Error::custom(format!(
"Invalid epoch number: {}",
e
))
})?;
epoch_number = Some(number.into());
break;
} else {
return Err(Error::custom(
"Invalid block number: missing 0x prefix"
.to_string(),
));
}
}
"blockHash" => {
block_hash = Some(visitor.next_value()?);
}
"requirePivot" => {
require_pivot = visitor.next_value()?;
}
key => {
return Err(Error::custom(format!(
"Unknown key: {}",
key
)))
}
},
None => break,
};
}
if let Some(number) = epoch_number {
return Ok(BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num(
number.into(),
)));
}
if let Some(hash) = block_hash {
return Ok(BlockHashOrEpochNumber::BlockHashWithOption {
hash,
require_pivot: Some(require_pivot),
});
}
return Err(Error::custom("Invalid input"));
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where E: Error {
if value.starts_with("hash:0x") {
Ok(BlockHashOrEpochNumber::BlockHashWithOption {
hash: value[7..].parse().map_err(Error::custom)?,
require_pivot: None,
})
} else {
value.parse().map_err(Error::custom).map(
|epoch_number: EpochNumber| {
BlockHashOrEpochNumber::EpochNumber(epoch_number)
},
)
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where E: Error {
self.visit_str(value.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
use std::str::FromStr;
#[test]
fn block_hash_or_epoch_number_deserialization() {
let s = r#"[
"0xa",
"latest_state",
"earliest",
"latest_mined",
"latest_checkpoint",
"latest_confirmed",
"latest_finalized",
"hash:0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
{"epochNumber": "0xa"},
{"blockHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"},
{"blockHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "requirePivot": false}
]"#;
let deserialized: Vec<BlockHashOrEpochNumber> =
serde_json::from_str(s).unwrap();
assert_eq!(
deserialized,
vec![
BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num((10).into())),
BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestState),
BlockHashOrEpochNumber::EpochNumber(EpochNumber::Earliest),
BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestMined),
BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestCheckpoint),
BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestConfirmed),
BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestFinalized),
BlockHashOrEpochNumber::BlockHashWithOption {
hash: H256::from_str(
"1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
)
.unwrap(),
require_pivot: None
},
BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num((10).into())),
BlockHashOrEpochNumber::BlockHashWithOption {
hash: H256::from_str(
"1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
)
.unwrap(),
require_pivot: Some(true)
},
BlockHashOrEpochNumber::BlockHashWithOption {
hash: H256::from_str(
"1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
)
.unwrap(),
require_pivot: Some(false)
}
]
)
}
#[test]
fn should_not_deserialize() {
let s = r#"[{}, "10"]"#;
assert!(serde_json::from_str::<Vec<BlockHashOrEpochNumber>>(s).is_err());
}
}