stratus/eth/primitives/
block_filter.rs

1use std::fmt::Display;
2
3use display_json::DebugAsJson;
4
5use super::PointInTime;
6use crate::alias::JsonValue;
7use crate::eth::primitives::BlockNumber;
8use crate::eth::primitives::Hash;
9
10#[derive(DebugAsJson, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, Hash)]
11#[cfg_attr(test, derive(fake::Dummy))]
12pub enum BlockFilter {
13    /// Information from the last mined block.
14    #[default]
15    Latest,
16
17    /// Information from the block being mined.
18    Pending,
19
20    /// Information from the first block.
21    Earliest,
22
23    /// Retrieve a block by its hash.
24    Hash(Hash),
25
26    /// Retrieve a block by its number.
27    Number(BlockNumber),
28}
29
30impl Display for BlockFilter {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            BlockFilter::Latest => write!(f, "latest"),
34            BlockFilter::Pending => write!(f, "pending"),
35            BlockFilter::Earliest => write!(f, "earliest"),
36            BlockFilter::Hash(block_hash) => write!(f, "{block_hash}"),
37            BlockFilter::Number(block_number) => write!(f, "{block_number}"),
38        }
39    }
40}
41
42impl From<PointInTime> for BlockFilter {
43    fn from(point_in_time: PointInTime) -> Self {
44        match point_in_time {
45            PointInTime::Mined => Self::Latest,
46            PointInTime::Pending => Self::Pending,
47            PointInTime::MinedPast(number) => Self::Number(number),
48        }
49    }
50}
51
52// -----------------------------------------------------------------------------
53// Serialization / Deserilization
54// -----------------------------------------------------------------------------
55
56impl<'de> serde::Deserialize<'de> for BlockFilter {
57    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58    where
59        D: serde::Deserializer<'de>,
60    {
61        let value = JsonValue::deserialize(deserializer)?;
62        match value {
63            // default
64            JsonValue::Null => Ok(Self::Latest),
65
66            // number
67            serde_json::Value::Number(number) => match number.as_u64() {
68                Some(number) => Ok(Self::Number(BlockNumber::from(number))),
69                None => Err(serde::de::Error::custom("block filter must be zero or a positive integer")),
70            },
71
72            // string
73            serde_json::Value::String(value) => {
74                match value.as_str() {
75                    // parse special keywords
76                    "latest" | "Latest" => Ok(Self::Latest),
77                    "pending" | "Pending" => Ok(Self::Pending),
78                    "earliest" | "Earliest" => Ok(Self::Earliest),
79
80                    // parse hash (64: H256 without 0x prefix; 66: H256 with 0x prefix)
81                    s if s.len() == 64 || s.len() == 66 => {
82                        let hash: Hash = s.parse().map_err(serde::de::Error::custom)?;
83                        Ok(Self::Hash(hash))
84                    }
85                    // parse number
86                    s => {
87                        let number: BlockNumber = s.parse().map_err(serde::de::Error::custom)?;
88                        Ok(Self::Number(number))
89                    }
90                }
91            }
92
93            serde_json::Value::Object(map) => {
94                if map.len() != 1 {
95                    return Err(serde::de::Error::custom("value was an object with an unexpected number of fields"));
96                }
97                let Some((key, value)) = map.iter().next() else {
98                    return Err(serde::de::Error::custom("value was an object with no fields"));
99                };
100                let Some(value_str) = value.as_str() else {
101                    return Err(serde::de::Error::custom("value was an object with non-str fields"));
102                };
103                match key.as_str() {
104                    "Hash" => {
105                        let hash: Hash = value_str.parse().map_err(serde::de::Error::custom)?;
106                        Ok(Self::Hash(hash))
107                    }
108                    "Number" => {
109                        let number: BlockNumber = value_str.parse().map_err(serde::de::Error::custom)?;
110                        Ok(Self::Number(number))
111                    }
112                    _ => Err(serde::de::Error::custom(
113                        "value was an object but its field was neither \"Hash\" nor \"Number\"",
114                    )),
115                }
116            }
117
118            // unhandled type
119            _ => Err(serde::de::Error::custom("block filter must be a string or integer")),
120        }
121    }
122}
123
124// -----------------------------------------------------------------------------
125// Tests
126// -----------------------------------------------------------------------------
127
128#[cfg(test)]
129mod tests {
130    use serde_json::json;
131
132    use crate::eth::primitives::*;
133
134    #[test]
135    fn serde_block_number_with_latest() {
136        let json = json!("latest");
137        assert_eq!(serde_json::from_value::<BlockFilter>(json).unwrap(), BlockFilter::Latest);
138    }
139
140    #[test]
141    fn serde_block_number_with_number() {
142        let json = json!("0x2");
143        assert_eq!(serde_json::from_value::<BlockFilter>(json).unwrap(), BlockFilter::Number(2usize.into()));
144    }
145}