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::BlockTimestampFilter;
9use crate::eth::primitives::Hash;
10
11#[derive(DebugAsJson, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, Hash)]
12#[cfg_attr(test, derive(fake::Dummy))]
13pub enum BlockFilter {
14    /// Information from the last mined block.
15    #[default]
16    Latest,
17
18    /// Information from the block being mined.
19    Pending,
20
21    /// Information from the first block.
22    Earliest,
23
24    /// Retrieve a block by its hash.
25    Hash(Hash),
26
27    /// Retrieve a block by its number.
28    Number(BlockNumber),
29
30    /// Retrieve a block by its timestamp.
31    Timestamp(BlockTimestampFilter),
32}
33
34impl Display for BlockFilter {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        match self {
37            BlockFilter::Latest => write!(f, "latest"),
38            BlockFilter::Pending => write!(f, "pending"),
39            BlockFilter::Earliest => write!(f, "earliest"),
40            BlockFilter::Hash(block_hash) => write!(f, "{block_hash}"),
41            BlockFilter::Number(block_number) => write!(f, "{block_number}"),
42            BlockFilter::Timestamp(timestamp) => write!(f, "{timestamp:?}"),
43        }
44    }
45}
46
47impl From<PointInTime> for BlockFilter {
48    fn from(point_in_time: PointInTime) -> Self {
49        match point_in_time {
50            PointInTime::Mined => Self::Latest,
51            PointInTime::Pending => Self::Pending,
52            PointInTime::MinedPast(number) => Self::Number(number),
53        }
54    }
55}
56
57// -----------------------------------------------------------------------------
58// Serialization / Deserilization
59// -----------------------------------------------------------------------------
60
61impl<'de> serde::Deserialize<'de> for BlockFilter {
62    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63    where
64        D: serde::Deserializer<'de>,
65    {
66        let value = JsonValue::deserialize(deserializer)?;
67        match value {
68            // default
69            JsonValue::Null => Ok(Self::Latest),
70
71            // number
72            serde_json::Value::Number(number) => match number.as_u64() {
73                Some(number) => Ok(Self::Number(BlockNumber::from(number))),
74                None => Err(serde::de::Error::custom("block filter must be zero or a positive integer")),
75            },
76
77            // string
78            serde_json::Value::String(value) => {
79                match value.as_str() {
80                    // parse special keywords
81                    "latest" | "Latest" => Ok(Self::Latest),
82                    "pending" | "Pending" => Ok(Self::Pending),
83                    "earliest" | "Earliest" => Ok(Self::Earliest),
84
85                    // parse hash (64: H256 without 0x prefix; 66: H256 with 0x prefix)
86                    s if s.len() == 64 || s.len() == 66 => {
87                        let hash: Hash = s.parse().map_err(serde::de::Error::custom)?;
88                        Ok(Self::Hash(hash))
89                    }
90                    // parse number
91                    s => {
92                        let number: BlockNumber = s.parse().map_err(serde::de::Error::custom)?;
93                        Ok(Self::Number(number))
94                    }
95                }
96            }
97
98            serde_json::Value::Object(map) => {
99                // Check if this is a timestamp filter object with "timestamp" and optional "mode" fields
100                if map.contains_key("timestamp") {
101                    let timestamp_filter: BlockTimestampFilter = serde_json::from_value(serde_json::Value::Object(map))
102                        .map_err(|e| serde::de::Error::custom(format!("failed to parse timestamp filter: {e}")))?;
103                    return Ok(Self::Timestamp(timestamp_filter));
104                }
105
106                // Otherwise, handle single-field object format like {"Hash": "..."} or {"Number": "..."}
107                if map.len() != 1 {
108                    return Err(serde::de::Error::custom("value was an object with an unexpected number of fields"));
109                }
110                let Some((key, value)) = map.iter().next() else {
111                    return Err(serde::de::Error::custom("value was an object with no fields"));
112                };
113
114                match key.as_str() {
115                    "Hash" => {
116                        let Some(value_str) = value.as_str() else {
117                            return Err(serde::de::Error::custom("Hash field must be a string"));
118                        };
119                        let hash: Hash = value_str.parse().map_err(serde::de::Error::custom)?;
120                        Ok(Self::Hash(hash))
121                    }
122                    "Number" => {
123                        let Some(value_str) = value.as_str() else {
124                            return Err(serde::de::Error::custom("Number field must be a string"));
125                        };
126                        let number: BlockNumber = value_str.parse().map_err(serde::de::Error::custom)?;
127                        Ok(Self::Number(number))
128                    }
129                    "Timestamp" => {
130                        // Handle {"Timestamp": {...}} format
131                        let timestamp_filter: BlockTimestampFilter =
132                            serde_json::from_value(value.clone()).map_err(|e| serde::de::Error::custom(format!("failed to parse timestamp filter: {e}")))?;
133                        Ok(Self::Timestamp(timestamp_filter))
134                    }
135                    _ => Err(serde::de::Error::custom(
136                        "value was an object but its field was not one of \"Hash\", \"Number\", \"Timestamp\", or \"timestamp\"",
137                    )),
138                }
139            }
140
141            // unhandled type
142            _ => Err(serde::de::Error::custom("block filter must be a string or integer")),
143        }
144    }
145}
146
147// -----------------------------------------------------------------------------
148// Tests
149// -----------------------------------------------------------------------------
150
151#[cfg(test)]
152mod tests {
153    use serde_json::json;
154
155    use crate::eth::primitives::*;
156
157    #[test]
158    fn serde_block_number_with_latest() {
159        let json = json!("latest");
160        assert_eq!(serde_json::from_value::<BlockFilter>(json).unwrap(), BlockFilter::Latest);
161    }
162
163    #[test]
164    fn serde_block_number_with_number() {
165        let json = json!("0x2");
166        assert_eq!(serde_json::from_value::<BlockFilter>(json).unwrap(), BlockFilter::Number(2usize.into()));
167    }
168
169    #[test]
170    fn serde_block_filter_with_timestamp_lowercase() {
171        let json = json!({"timestamp": 1234567890});
172        let result = serde_json::from_value::<BlockFilter>(json).unwrap();
173        match result {
174            BlockFilter::Timestamp(filter) => {
175                assert_eq!(*filter.timestamp, 1234567890);
176                assert_eq!(filter.mode, BlockTimestampSeekMode::ExactOrPrevious);
177            }
178            _ => panic!("Expected BlockFilter::Timestamp"),
179        }
180    }
181
182    #[test]
183    fn serde_block_filter_with_timestamp_and_mode() {
184        let json = json!({"timestamp": 1234567890, "mode": "exactOrNext"});
185        let result = serde_json::from_value::<BlockFilter>(json).unwrap();
186        match result {
187            BlockFilter::Timestamp(filter) => {
188                assert_eq!(*filter.timestamp, 1234567890);
189                assert_eq!(filter.mode, BlockTimestampSeekMode::ExactOrNext);
190            }
191            _ => panic!("Expected BlockFilter::Timestamp"),
192        }
193    }
194
195    #[test]
196    fn serde_block_filter_with_timestamp_capitalized() {
197        let json = json!({"Timestamp": {"timestamp": 1234567890}});
198        let result = serde_json::from_value::<BlockFilter>(json).unwrap();
199        match result {
200            BlockFilter::Timestamp(filter) => {
201                assert_eq!(*filter.timestamp, 1234567890);
202                assert_eq!(filter.mode, BlockTimestampSeekMode::ExactOrPrevious);
203            }
204            _ => panic!("Expected BlockFilter::Timestamp"),
205        }
206    }
207
208    #[test]
209    fn serde_block_filter_with_timestamp_capitalized_and_mode() {
210        let json = json!({"Timestamp": {"timestamp": 1234567890, "mode": "exactOrPrevious"}});
211        let result = serde_json::from_value::<BlockFilter>(json).unwrap();
212        match result {
213            BlockFilter::Timestamp(filter) => {
214                assert_eq!(*filter.timestamp, 1234567890);
215                assert_eq!(filter.mode, BlockTimestampSeekMode::ExactOrPrevious);
216            }
217            _ => panic!("Expected BlockFilter::Timestamp"),
218        }
219    }
220}