hypersync_net_types/query.rs
1use crate::block::{BlockField, BlockSelection};
2use crate::hypersync_net_types_capnp::{query_body, request};
3use crate::log::{LogField, LogSelection};
4use crate::trace::{TraceField, TraceSelection};
5use crate::transaction::{TransactionField, TransactionSelection};
6use crate::types::AnyOf;
7use crate::{hypersync_net_types_capnp, CapnpBuilder, CapnpReader};
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeSet;
10
11/// A hypersync query that defines what blockchain data to retrieve.
12///
13/// The `Query` struct provides a fluent builder API for constructing complex blockchain queries
14/// using hypersync. It allows you to specify block ranges, filter by logs/transactions/traces/blocks,
15/// select which fields to return, and control query behavior like join modes.
16///
17/// # Core Concepts
18///
19/// - **Block Range**: Define the range of blocks to query with `from_block` and optional `to_block`
20/// - **Filters**: Specify what data to match using `LogFilter`, `TransactionFilter`, `BlockFilter`, and `TraceFilter`
21/// - **Field Selection**: Choose which fields to include in the response to optimize performance
22/// - **Join Modes**: Control how different data types are related and joined
23///
24/// # Performance Tips
25///
26/// - Specify the minimum `FieldSelection` to only request the data you need
27/// - Use specific filters rather than broad queries when possible
28/// - Be mindful of setting `include_all_blocks: true` as it can significantly increase response size
29/// - Traces are only available on select hypersync instances
30///
31/// # Basic Examples
32///
33/// ```
34/// use hypersync_net_types::{
35/// Query, FieldSelection, LogFilter, BlockFilter, TransactionFilter,
36/// block::BlockField, log::LogField, transaction::TransactionField
37/// };
38///
39/// // Simple log query for USDT transfers
40/// let usdt_transfers = Query::new()
41/// .from_block(18_000_000)
42/// .to_block_excl(18_001_000)
43/// .select_block_fields([BlockField::Number, BlockField::Timestamp])
44/// .select_log_fields([LogField::Address, LogField::Data, LogField::Topic0, LogField::Topic1, LogField::Topic2])
45/// .select_transaction_fields([TransactionField::Hash, TransactionField::From, TransactionField::To])
46/// .where_logs(
47/// LogFilter::all()
48/// .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])? // USDT contract
49/// .and_topic0(["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"])? // Transfer event
50/// );
51/// # Ok::<(), anyhow::Error>(())
52/// ```
53///
54/// # Advanced Examples
55///
56/// ```
57/// use hypersync_net_types::{
58/// Query, FieldSelection, JoinMode, LogFilter, TransactionFilter, Selection,
59/// block::BlockField, log::LogField, transaction::TransactionField
60/// };
61///
62/// // Complex query with multiple different filter combinations and exclusions
63/// let complex_query = Query::new()
64/// .from_block(18_000_000)
65/// .to_block_excl(18_010_000)
66/// .join_mode(JoinMode::JoinAll)
67/// .select_block_fields(BlockField::all())
68/// .select_transaction_fields(TransactionField::all())
69/// .select_log_fields(LogField::all())
70/// .where_logs(
71/// // Transfer events from USDT and USDC contracts (multiple addresses in one filter)
72/// LogFilter::all()
73/// .and_address([
74/// "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT
75/// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // USDC
76/// ])?
77/// .and_topic0(["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"])? // Transfer event
78/// .or(
79/// // Approval events from any ERC20 contract (different topic combination)
80/// LogFilter::all()
81/// .and_topic0(["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"])? // Approval event
82/// )
83/// .or(
84/// // Swap events from Uniswap V2 pairs (another distinct filter combination)
85/// LogFilter::all()
86/// .and_topic0(["0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822"])? // Swap event
87/// )
88/// )
89/// .where_transactions(
90/// TransactionFilter::all()
91/// .and_sighash([
92/// "0xa9059cbb", // transfer(address,uint256)
93/// "0x095ea7b3", // approve(address,uint256)
94/// ])?
95/// .or(TransactionFilter::all().and_status(0)) // Failed transactions
96/// );
97/// # Ok::<(), anyhow::Error>(())
98/// ```
99///
100/// # Exclusion with `and_not`
101///
102/// ```
103/// use hypersync_net_types::{Query, FieldSelection, LogFilter, Selection, log::LogField};
104///
105/// // Query for ERC20 transfers but exclude specific problematic contracts
106/// let filtered_query = Query::new()
107/// .from_block(18_000_000)
108/// .to_block_excl(18_001_000)
109/// .select_log_fields([LogField::Address, LogField::Data, LogField::Topic0, LogField::Topic1, LogField::Topic2])
110/// .where_logs(
111/// // Include Transfer events from all contracts, but exclude specific problematic contracts
112/// Selection::new(
113/// LogFilter::all()
114/// .and_topic0(["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"])? // Transfer event
115/// )
116/// // But exclude specific problematic contracts
117/// .and_not(
118/// LogFilter::all()
119/// .and_address([
120/// "0x1234567890123456789012345678901234567890", // Problematic contract 1
121/// "0x0987654321098765432109876543210987654321", // Problematic contract 2
122/// ])?
123/// )
124/// );
125/// # Ok::<(), anyhow::Error>(())
126/// ```
127///
128/// # Builder Pattern
129///
130/// The Query struct uses a fluent builder pattern where all methods return `Self`, allowing for easy chaining:
131///
132/// ```
133/// use hypersync_net_types::{Query, FieldSelection, JoinMode, block::BlockField};
134///
135/// let query = Query::new()
136/// .from_block(18_000_000)
137/// .to_block_excl(18_010_000)
138/// .join_mode(JoinMode::Default)
139/// .include_all_blocks()
140/// .select_block_fields(BlockField::all());
141/// ```
142#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
143#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
144pub struct Query {
145 /// The block to start the query from
146 pub from_block: u64,
147 /// The block to end the query at. If not specified, the query will go until the
148 /// end of data. Exclusive, the returned range will be [from_block..to_block).
149 ///
150 /// The query will return before it reaches this target block if it hits the time limit
151 /// configured on the server. The user should continue their query by putting the
152 /// next_block field in the response into from_block field of their next query. This implements
153 /// pagination.
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub to_block: Option<u64>,
156 #[serde(default, skip_serializing_if = "Vec::is_empty")]
157 pub logs: Vec<LogSelection>,
158 /// List of transaction selections, the query will return transactions that match any of these selections
159 #[serde(default, skip_serializing_if = "Vec::is_empty")]
160 pub transactions: Vec<TransactionSelection>,
161 /// List of trace selections, the query will return traces that match any of these selections
162 #[serde(default, skip_serializing_if = "Vec::is_empty")]
163 pub traces: Vec<TraceSelection>,
164 /// List of block selections, the query will return blocks that match any of these selections
165 #[serde(default, skip_serializing_if = "Vec::is_empty")]
166 pub blocks: Vec<BlockSelection>,
167 /// Whether to include all blocks regardless of if they are related to a returned transaction or log. Normally
168 /// the server will return only the blocks that are related to the transaction or logs in the response. But if this
169 /// is set to true, the server will return data for all blocks in the requested range [from_block, to_block).
170 #[serde(default, skip_serializing_if = "is_default")]
171 pub include_all_blocks: bool,
172 /// Field selection. The user can select which fields they are interested in, requesting less fields will improve
173 /// query execution time and reduce the payload size so the user should always use a minimal number of fields.
174 #[serde(default, skip_serializing_if = "is_default")]
175 pub field_selection: FieldSelection,
176 /// Maximum number of blocks that should be returned, the server might return more blocks than this number but
177 /// it won't overshoot by too much.
178 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub max_num_blocks: Option<usize>,
180 /// Maximum number of transactions that should be returned, the server might return more transactions than this number but
181 /// it won't overshoot by too much.
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub max_num_transactions: Option<usize>,
184 /// Maximum number of logs that should be returned, the server might return more logs than this number but
185 /// it won't overshoot by too much.
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub max_num_logs: Option<usize>,
188 /// Maximum number of traces that should be returned, the server might return more traces than this number but
189 /// it won't overshoot by too much.
190 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub max_num_traces: Option<usize>,
192 /// Selects join mode for the query,
193 /// Default: join in this order logs -> transactions -> traces -> blocks
194 /// JoinAll: join everything to everything. For example if logSelection matches log0, we get the
195 /// associated transaction of log0 and then we get associated logs of that transaction as well. Applies similarly
196 /// to blocks, traces.
197 /// JoinNothing: join nothing.
198 #[serde(default, skip_serializing_if = "is_default")]
199 pub join_mode: JoinMode,
200}
201
202/// Used to skip serializing a defaulted serde field if
203/// the value matches the default value.
204fn is_default<T: Default + PartialEq>(t: &T) -> bool {
205 t == &T::default()
206}
207
208#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy)]
209#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
210pub enum JoinMode {
211 /// Join in this order logs -> transactions -> traces -> blocks
212 #[default]
213 Default,
214 /// Join everything to everything. For example if logSelection matches log0, we get the
215 /// associated transaction of log0 and then we get associated logs of that transaction as well. Applies similarly
216 /// to blocks, traces.
217 JoinAll,
218 /// JoinNothing: join nothing.
219 JoinNothing,
220}
221
222impl Query {
223 pub fn new() -> Self {
224 Default::default()
225 }
226
227 /// Set the starting block number the query should execute from.
228 /// This is inclusive, meaning the query will include the given block number.
229 /// If not specified, the query will start from the the genesis block.
230 pub fn from_block(mut self, block_number: u64) -> Self {
231 self.from_block = block_number;
232 self
233 }
234
235 /// Set the the ending block number the query should execute to.
236 /// This is exclusive, meaning the query will execute up to but not including the given block number.
237 ///
238 /// eg. the following will return blocks 0 to 99.
239 ///```
240 /// use hypersync_net_types::Query;
241 /// Query::new().to_block_excl(100);
242 /// ```
243 /// If not specified, the query will end at the tip of the chain.
244 pub fn to_block_excl(mut self, block_number: u64) -> Self {
245 self.to_block = Some(block_number);
246 self
247 }
248
249 /// Set log filters that the query will match against.
250 ///
251 /// This method accepts any iterable of items that can be converted to `LogSelection`.
252 /// Common input types include `LogFilter` objects and `LogSelection` objects.
253 /// The query will return logs that match any of the provided filters.
254 ///
255 /// # Arguments
256 /// * `logs` - An iterable of log filters/selections to match
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use hypersync_net_types::{Query, LogFilter};
262 ///
263 /// // Single filter for logs from multiple contracts
264 /// let query = Query::new()
265 /// .from_block(18_000_000)
266 /// .where_logs(
267 /// LogFilter::all()
268 /// .and_address([
269 /// "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT
270 /// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // USDC
271 /// ])?
272 /// );
273 ///
274 /// // Multiple different filter combinations using .or()
275 /// let query = Query::new()
276 /// .where_logs(
277 /// // Transfer events from specific contracts
278 /// LogFilter::all()
279 /// .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?
280 /// .and_topic0(["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"])? // Transfer
281 /// .or(
282 /// // Approval events from any contract
283 /// LogFilter::all()
284 /// .and_topic0(["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"])? // Approval
285 /// )
286 /// );
287 /// # Ok::<(), anyhow::Error>(())
288 /// ```
289 pub fn where_logs<I, T>(mut self, logs: I) -> Self
290 where
291 I: Into<AnyOf<T>>,
292 T: Into<LogSelection>,
293 {
294 let any_clause: AnyOf<T> = logs.into();
295 let log_selection: Vec<LogSelection> = any_clause.into_iter().map(Into::into).collect();
296 self.logs = log_selection;
297 self
298 }
299
300 /// Set block filters that the query will match against.
301 ///
302 /// This method accepts any iterable of items that can be converted to `BlockSelection`.
303 /// Common input types include `BlockFilter` objects and `BlockSelection` objects.
304 /// The query will return blocks that match any of the provided filters.
305 ///
306 /// # Arguments
307 /// * `blocks` - An iterable of block filters/selections to match
308 ///
309 /// # Examples
310 ///
311 /// ```
312 /// use hypersync_net_types::{Query, BlockFilter};
313 ///
314 /// // Single filter for blocks by specific hashes
315 /// let query = Query::new()
316 /// .from_block(18_000_000)
317 /// .where_blocks(
318 /// BlockFilter::all()
319 /// .and_hash(["0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294"])?
320 /// );
321 ///
322 /// // Multiple filter combinations using .or()
323 /// let query = Query::new()
324 /// .where_blocks(
325 /// BlockFilter::all()
326 /// .and_hash(["0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294"])?
327 /// .or(
328 /// BlockFilter::all()
329 /// .and_miner([
330 /// "0xdac17f958d2ee523a2206206994597c13d831ec7", // Mining pool 1
331 /// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // Mining pool 2
332 /// ])?
333 /// )
334 /// );
335 /// # Ok::<(), anyhow::Error>(())
336 /// ```
337 pub fn where_blocks<I, T>(mut self, blocks: I) -> Self
338 where
339 I: Into<AnyOf<T>>,
340 T: Into<BlockSelection>,
341 {
342 let any_clause: AnyOf<T> = blocks.into();
343 let block_selections: Vec<BlockSelection> =
344 any_clause.into_iter().map(Into::into).collect();
345 self.blocks = block_selections;
346 self
347 }
348
349 /// Set transaction filters that the query will match against.
350 ///
351 /// This method accepts any iterable of items that can be converted to `TransactionSelection`.
352 /// Common input types include `TransactionFilter` objects and `TransactionSelection` objects.
353 /// The query will return transactions that match any of the provided filters.
354 ///
355 /// # Arguments
356 /// * `transactions` - An iterable of transaction filters/selections to match
357 ///
358 /// # Examples
359 ///
360 /// ```
361 /// use hypersync_net_types::{Query, TransactionFilter};
362 ///
363 /// // Single filter for transactions from specific addresses
364 /// let query = Query::new()
365 /// .from_block(18_000_000)
366 /// .where_transactions(
367 /// TransactionFilter::all()
368 /// .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
369 /// );
370 ///
371 /// // Multiple filter combinations using .or()
372 /// let transfer_sig = "0xa9059cbb"; // transfer(address,uint256)
373 /// let query = Query::new()
374 /// .where_transactions(
375 /// TransactionFilter::all().and_sighash([transfer_sig])?
376 /// .or(
377 /// TransactionFilter::all().and_status(0) // Failed transactions
378 /// )
379 /// );
380 /// # Ok::<(), anyhow::Error>(())
381 /// ```
382 pub fn where_transactions<I, T>(mut self, transactions: I) -> Self
383 where
384 I: Into<AnyOf<T>>,
385 T: Into<TransactionSelection>,
386 {
387 let any_clause: AnyOf<T> = transactions.into();
388 let transaction_selections: Vec<TransactionSelection> =
389 any_clause.into_iter().map(Into::into).collect();
390 self.transactions = transaction_selections;
391 self
392 }
393
394 /// Set trace filters that the query will match against.
395 ///
396 /// This method accepts any iterable of items that can be converted to `TraceSelection`.
397 /// Common input types include `TraceFilter` objects and `TraceSelection` objects.
398 /// The query will return traces that match any of the provided filters.
399 ///
400 /// # Availability
401 /// **Note**: Trace data is only available on select hypersync instances. Not all blockchain
402 /// networks provide trace data, and it requires additional infrastructure to collect and serve.
403 /// Check your hypersync instance documentation to confirm trace availability for your target network.
404 ///
405 /// # Arguments
406 /// * `traces` - An iterable of trace filters/selections to match
407 ///
408 /// # Examples
409 ///
410 /// ```
411 /// use hypersync_net_types::{Query, TraceFilter};
412 ///
413 /// // Single filter for traces from specific caller addresses
414 /// let query = Query::new()
415 /// .from_block(18_000_000)
416 /// .where_traces(
417 /// TraceFilter::all()
418 /// .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
419 /// );
420 ///
421 /// // Multiple filter combinations using .or()
422 /// let transfer_sig = "0xa9059cbb"; // transfer(address,uint256)
423 /// let query = Query::new()
424 /// .where_traces(
425 /// TraceFilter::all().and_call_type(["create", "suicide"])
426 /// .or(
427 /// TraceFilter::all().and_sighash([transfer_sig])?
428 /// )
429 /// );
430 /// # Ok::<(), anyhow::Error>(())
431 /// ```
432 pub fn where_traces<I, T>(mut self, traces: I) -> Self
433 where
434 I: Into<AnyOf<T>>,
435 T: Into<TraceSelection>,
436 {
437 let any_clause: AnyOf<T> = traces.into();
438 let trace_selections: Vec<TraceSelection> =
439 any_clause.into_iter().map(Into::into).collect();
440 self.traces = trace_selections;
441 self
442 }
443
444 /// Select specific log fields to include in query results.
445 ///
446 /// This method allows you to specify which log fields should be returned in the query response.
447 /// Only the selected fields will be included, which can improve performance and reduce payload size.
448 /// This replaces the previous `select_fields(FieldSelection::new().log(...))` pattern.
449 ///
450 /// # Arguments
451 /// * `fields` - An iterable of `LogField` values to select
452 ///
453 /// # Examples
454 ///
455 /// ```
456 /// use hypersync_net_types::{Query, log::LogField, transaction::TransactionField};
457 ///
458 /// // Select essential log fields
459 /// let query = Query::new()
460 /// .from_block(18_000_000)
461 /// .select_log_fields([LogField::Address, LogField::Data, LogField::Topic0]);
462 ///
463 /// // Select all log fields for comprehensive event data
464 /// let query = Query::new()
465 /// .select_log_fields(LogField::all());
466 ///
467 /// // Select all topic fields for event analysis
468 /// let query = Query::new()
469 /// .select_log_fields([
470 /// LogField::Topic0,
471 /// LogField::Topic1,
472 /// LogField::Topic2,
473 /// LogField::Topic3,
474 /// ]);
475 ///
476 /// // Chain with other field selections
477 /// let query = Query::new()
478 /// .select_log_fields([LogField::Address, LogField::Data])
479 /// .select_transaction_fields([TransactionField::Hash]);
480 /// ```
481 pub fn select_log_fields<I>(mut self, fields: I) -> Self
482 where
483 I: IntoIterator,
484 I::Item: Into<LogField>,
485 {
486 self.field_selection.log = fields.into_iter().map(Into::into).collect();
487 self
488 }
489
490 /// Select specific transaction fields to include in query results.
491 ///
492 /// This method allows you to specify which transaction fields should be returned in the query response.
493 /// Only the selected fields will be included, which can improve performance and reduce payload size.
494 /// This replaces the previous `select_fields(FieldSelection::new().transaction(...))` pattern.
495 ///
496 /// # Arguments
497 /// * `fields` - An iterable of `TransactionField` values to select
498 ///
499 /// # Examples
500 ///
501 /// ```
502 /// use hypersync_net_types::{Query, transaction::TransactionField, block::BlockField};
503 ///
504 /// // Select specific transaction fields
505 /// let query = Query::new()
506 /// .from_block(18_000_000)
507 /// .select_transaction_fields([TransactionField::Hash, TransactionField::From, TransactionField::To]);
508 ///
509 /// // Select all transaction fields for complete transaction data
510 /// let query = Query::new()
511 /// .select_transaction_fields(TransactionField::all());
512 ///
513 /// // Select fields related to gas and value
514 /// let query = Query::new()
515 /// .select_transaction_fields([
516 /// TransactionField::GasPrice,
517 /// TransactionField::GasUsed,
518 /// TransactionField::Value,
519 /// ]);
520 ///
521 /// // Chain with other field selections
522 /// let query = Query::new()
523 /// .select_transaction_fields([TransactionField::Hash, TransactionField::Value])
524 /// .select_block_fields([BlockField::Number]);
525 /// ```
526 pub fn select_transaction_fields<I>(mut self, fields: I) -> Self
527 where
528 I: IntoIterator,
529 I::Item: Into<TransactionField>,
530 {
531 self.field_selection.transaction = fields.into_iter().map(Into::into).collect();
532 self
533 }
534 /// Select specific trace fields to include in query results.
535 ///
536 /// This method allows you to specify which trace fields should be returned in the query response.
537 /// Only the selected fields will be included, which can improve performance and reduce payload size.
538 /// This replaces the previous `select_fields(FieldSelection::new().trace(...))` pattern.
539 ///
540 /// # Availability
541 /// **Note**: Trace data is only available on select hypersync instances. Not all blockchain
542 /// networks provide trace data, and it requires additional infrastructure to collect and serve.
543 /// Check your hypersync instance documentation to confirm trace availability for your target network.
544 ///
545 /// # Arguments
546 /// * `fields` - An iterable of `TraceField` values to select
547 ///
548 /// # Examples
549 ///
550 /// ```
551 /// use hypersync_net_types::{Query, trace::TraceField, log::LogField};
552 ///
553 /// // Select basic trace information
554 /// let query = Query::new()
555 /// .from_block(18_000_000)
556 /// .select_trace_fields([TraceField::From, TraceField::To, TraceField::Value]);
557 ///
558 /// // Select all trace fields for comprehensive trace analysis
559 /// let query = Query::new()
560 /// .select_trace_fields(TraceField::all());
561 ///
562 /// // Select trace execution details
563 /// let query = Query::new()
564 /// .select_trace_fields([
565 /// TraceField::CallType,
566 /// TraceField::Input,
567 /// TraceField::Output,
568 /// TraceField::Gas,
569 /// TraceField::GasUsed,
570 /// ]);
571 ///
572 /// // Chain with other field selections
573 /// let query = Query::new()
574 /// .select_trace_fields([TraceField::From, TraceField::To])
575 /// .select_log_fields([LogField::Address]);
576 /// ```
577 pub fn select_trace_fields<I>(mut self, fields: I) -> Self
578 where
579 I: IntoIterator,
580 I::Item: Into<TraceField>,
581 {
582 self.field_selection.trace = fields.into_iter().map(Into::into).collect();
583 self
584 }
585
586 /// Select specific block fields to include in query results.
587 ///
588 /// This method allows you to specify which block fields should be returned in the query response.
589 /// Only the selected fields will be included, which can improve performance and reduce payload size.
590 /// This replaces the previous `select_fields(FieldSelection::new().block(...))` pattern.
591 ///
592 /// # Arguments
593 /// * `fields` - An iterable of `BlockField` values to select
594 ///
595 /// # Examples
596 ///
597 /// ```
598 /// use hypersync_net_types::{Query, block::BlockField, transaction::TransactionField};
599 ///
600 /// // Select specific block fields
601 /// let query = Query::new()
602 /// .from_block(18_000_000)
603 /// .select_block_fields([BlockField::Number, BlockField::Hash, BlockField::Timestamp]);
604 ///
605 /// // Select all block fields for comprehensive data
606 /// let query = Query::new()
607 /// .select_block_fields(BlockField::all());
608 ///
609 /// // Select essential block metadata
610 /// let query = Query::new()
611 /// .select_block_fields([
612 /// BlockField::Number,
613 /// BlockField::Hash,
614 /// BlockField::ParentHash,
615 /// BlockField::Timestamp,
616 /// ]);
617 ///
618 /// // Chain with other field selections
619 /// let query = Query::new()
620 /// .select_block_fields([BlockField::Number, BlockField::Timestamp])
621 /// .select_transaction_fields([TransactionField::Hash]);
622 /// ```
623 pub fn select_block_fields<I>(mut self, fields: I) -> Self
624 where
625 I: IntoIterator,
626 I::Item: Into<BlockField>,
627 {
628 self.field_selection.block = fields.into_iter().map(Into::into).collect();
629 self
630 }
631
632 /// Set the join mode for the query to control how different data types are related.
633 ///
634 /// Join mode determines how hypersync correlates data between blocks, transactions, logs, and traces.
635 /// This affects which additional related data is included in the response beyond what your filters directly match.
636 ///
637 /// # Arguments
638 /// * `join_mode` - The `JoinMode` to use for the query
639 ///
640 /// # Join Modes:
641 /// - `JoinMode::Default`: Join in order logs → transactions → traces → blocks
642 /// - `JoinMode::JoinAll`: Join everything to everything (comprehensive but larger responses)
643 /// - `JoinMode::JoinNothing`: No joins, return only directly matched data
644 ///
645 /// # Examples
646 ///
647 /// ```
648 /// use hypersync_net_types::{Query, JoinMode, LogFilter};
649 ///
650 /// // Default join mode - get transactions and blocks for matching logs
651 /// let query = Query::new()
652 /// .from_block(18_000_000)
653 /// .join_mode(JoinMode::Default)
654 /// .where_logs(LogFilter::all());
655 ///
656 /// // Join everything - comprehensive data for analysis
657 /// let query = Query::new()
658 /// .from_block(18_000_000)
659 /// .join_mode(JoinMode::JoinAll)
660 /// .where_logs(LogFilter::all());
661 ///
662 /// // No joins - only the exact logs that match the filter
663 /// let query = Query::new()
664 /// .from_block(18_000_000)
665 /// .join_mode(JoinMode::JoinNothing)
666 /// .where_logs(LogFilter::all());
667 /// ```
668 pub fn join_mode(mut self, join_mode: JoinMode) -> Self {
669 self.join_mode = join_mode;
670 self
671 }
672
673 /// Set whether to include all blocks in the requested range, regardless of matches.
674 ///
675 /// By default, hypersync only returns blocks that are related to matched transactions, logs, or traces.
676 /// Setting this to `true` forces the server to return data for all blocks in the range [from_block, to_block),
677 /// even if they don't contain any matching transactions or logs.
678 ///
679 /// # Use Cases:
680 /// - Block-level analytics requiring complete block data
681 /// - Ensuring no blocks are missed in sequential processing
682 /// - Getting block headers for every block in a range
683 ///
684 /// # Performance Note:
685 /// Setting this to `true` can significantly increase response size and processing time,
686 /// especially for large block ranges. Use judiciously.
687 ///
688 /// # Examples
689 ///
690 /// ```
691 /// use hypersync_net_types::{Query, LogFilter, FieldSelection, block::BlockField};
692 ///
693 /// // Include all blocks for complete block header data
694 /// let query = Query::new()
695 /// .from_block(18_000_000)
696 /// .to_block_excl(18_000_100)
697 /// .include_all_blocks()
698 /// .select_block_fields([BlockField::Number, BlockField::Hash, BlockField::Timestamp]);
699 ///
700 /// // Normal mode - only blocks with matching logs
701 /// let query = Query::new()
702 /// .from_block(18_000_000)
703 /// .where_logs(
704 /// LogFilter::all()
705 /// .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?
706 /// );
707 /// # Ok::<(), anyhow::Error>(())
708 /// ```
709 pub fn include_all_blocks(mut self) -> Self {
710 self.include_all_blocks = true;
711 self
712 }
713
714 pub fn from_capnp_query_body_reader(
715 query_body_reader: &query_body::Reader,
716 from_block: u64,
717 to_block: Option<u64>,
718 ) -> Result<Self, capnp::Error> {
719 let include_all_blocks = query_body_reader.get_include_all_blocks();
720
721 // Parse field selection
722 let field_selection = if query_body_reader.has_field_selection() {
723 let fs = query_body_reader.get_field_selection()?;
724 FieldSelection::from_reader(fs)?
725 } else {
726 FieldSelection::default()
727 };
728
729 // Parse max values using OptUInt64
730 let max_num_blocks = if query_body_reader.has_max_num_blocks() {
731 let max_blocks_reader = query_body_reader.get_max_num_blocks()?;
732 let value = max_blocks_reader.get_value();
733 Some(value as usize)
734 } else {
735 None
736 };
737 let max_num_transactions = if query_body_reader.has_max_num_transactions() {
738 let max_tx_reader = query_body_reader.get_max_num_transactions()?;
739 let value = max_tx_reader.get_value();
740 Some(value as usize)
741 } else {
742 None
743 };
744 let max_num_logs = if query_body_reader.has_max_num_logs() {
745 let max_logs_reader = query_body_reader.get_max_num_logs()?;
746 let value = max_logs_reader.get_value();
747 Some(value as usize)
748 } else {
749 None
750 };
751 let max_num_traces = if query_body_reader.has_max_num_traces() {
752 let max_traces_reader = query_body_reader.get_max_num_traces()?;
753 let value = max_traces_reader.get_value();
754 Some(value as usize)
755 } else {
756 None
757 };
758
759 // Parse join mode
760 let join_mode = match query_body_reader.get_join_mode()? {
761 hypersync_net_types_capnp::JoinMode::Default => JoinMode::Default,
762 hypersync_net_types_capnp::JoinMode::JoinAll => JoinMode::JoinAll,
763 hypersync_net_types_capnp::JoinMode::JoinNothing => JoinMode::JoinNothing,
764 };
765
766 // Parse selections
767 let mut logs = Vec::new();
768 if query_body_reader.has_logs() {
769 let logs_list = query_body_reader.get_logs()?;
770 for log_reader in logs_list {
771 logs.push(LogSelection::from_reader(log_reader)?);
772 }
773 }
774
775 let mut transactions = Vec::new();
776 if query_body_reader.has_transactions() {
777 let tx_list = query_body_reader.get_transactions()?;
778 for tx_reader in tx_list {
779 transactions.push(TransactionSelection::from_reader(tx_reader)?);
780 }
781 }
782
783 let mut traces = Vec::new();
784 if query_body_reader.has_traces() {
785 let traces_list = query_body_reader.get_traces()?;
786 for trace_reader in traces_list {
787 traces.push(TraceSelection::from_reader(trace_reader)?);
788 }
789 }
790
791 let mut blocks = Vec::new();
792 if query_body_reader.has_blocks() {
793 let blocks_list = query_body_reader.get_blocks()?;
794 for block_reader in blocks_list {
795 blocks.push(BlockSelection::from_reader(block_reader)?);
796 }
797 }
798
799 Ok(Self {
800 from_block,
801 to_block,
802 logs,
803 transactions,
804 traces,
805 blocks,
806 include_all_blocks,
807 field_selection,
808 max_num_blocks,
809 max_num_transactions,
810 max_num_logs,
811 max_num_traces,
812 join_mode,
813 })
814 }
815}
816
817#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
818#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
819pub struct FieldSelection {
820 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
821 pub block: BTreeSet<BlockField>,
822 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
823 pub transaction: BTreeSet<TransactionField>,
824 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
825 pub log: BTreeSet<LogField>,
826 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
827 pub trace: BTreeSet<TraceField>,
828}
829
830impl FieldSelection {
831 /// Create a new empty field selection.
832 ///
833 /// This creates a field selection with no fields selected. You can then use the builder
834 /// methods to select specific fields for each data type (block, transaction, log, trace).
835 ///
836 /// # Examples
837 ///
838 /// ```
839 /// use hypersync_net_types::FieldSelection;
840 ///
841 /// // Create empty field selection
842 /// let field_selection = FieldSelection::new();
843 ///
844 /// // All field sets are empty by default
845 /// assert!(field_selection.block.is_empty());
846 /// assert!(field_selection.transaction.is_empty());
847 /// assert!(field_selection.log.is_empty());
848 /// assert!(field_selection.trace.is_empty());
849 /// ```
850 pub fn new() -> Self {
851 Self::default()
852 }
853 /// Select specific block fields to include in query results.
854 ///
855 /// This method allows you to specify which block fields should be returned in the query response.
856 /// Only the selected fields will be included, which can improve performance and reduce payload size.
857 ///
858 /// # Arguments
859 /// * `fields` - An iterable of `BlockField` values to select
860 ///
861 /// # Examples
862 ///
863 /// ```
864 /// use hypersync_net_types::{FieldSelection, block::BlockField, transaction::TransactionField};
865 ///
866 /// // Select specific block fields
867 /// let field_selection = FieldSelection::new()
868 /// .block([BlockField::Number, BlockField::Hash, BlockField::Timestamp]);
869 ///
870 /// // Select all block fields for comprehensive data
871 /// let field_selection = FieldSelection::new()
872 /// .block(BlockField::all());
873 ///
874 /// // Can also use a vector for specific fields
875 /// let fields = vec![BlockField::Number, BlockField::ParentHash];
876 /// let field_selection = FieldSelection::new()
877 /// .block(fields);
878 ///
879 /// // Chain with other field selections - mix all and specific
880 /// let field_selection = FieldSelection::new()
881 /// .block(BlockField::all())
882 /// .transaction([TransactionField::Hash]);
883 /// ```
884 pub fn block<T: IntoIterator<Item = BlockField>>(mut self, fields: T) -> Self {
885 self.block.extend(fields);
886 self
887 }
888 /// Select specific transaction fields to include in query results.
889 ///
890 /// This method allows you to specify which transaction fields should be returned in the query response.
891 /// Only the selected fields will be included, which can improve performance and reduce payload size.
892 ///
893 /// # Arguments
894 /// * `fields` - An iterable of `TransactionField` values to select
895 ///
896 /// # Examples
897 ///
898 /// ```
899 /// use hypersync_net_types::{FieldSelection, transaction::TransactionField, block::BlockField};
900 ///
901 /// // Select specific transaction fields
902 /// let field_selection = FieldSelection::new()
903 /// .transaction([TransactionField::Hash, TransactionField::From, TransactionField::To]);
904 ///
905 /// // Select all transaction fields for complete transaction data
906 /// let field_selection = FieldSelection::new()
907 /// .transaction(TransactionField::all());
908 ///
909 /// // Select fields related to gas and value
910 /// let field_selection = FieldSelection::new()
911 /// .transaction([
912 /// TransactionField::GasPrice,
913 /// TransactionField::GasUsed,
914 /// TransactionField::Value,
915 /// ]);
916 ///
917 /// // Chain with other field selections - mix all and specific
918 /// let field_selection = FieldSelection::new()
919 /// .block([BlockField::Number])
920 /// .transaction(TransactionField::all());
921 /// ```
922 pub fn transaction<T: IntoIterator<Item = TransactionField>>(mut self, fields: T) -> Self {
923 self.transaction.extend(fields);
924 self
925 }
926 /// Select specific log fields to include in query results.
927 ///
928 /// This method allows you to specify which log fields should be returned in the query response.
929 /// Only the selected fields will be included, which can improve performance and reduce payload size.
930 ///
931 /// # Arguments
932 /// * `fields` - An iterable of `LogField` values to select
933 ///
934 /// # Examples
935 ///
936 /// ```
937 /// use hypersync_net_types::{FieldSelection, log::LogField, transaction::TransactionField};
938 ///
939 /// // Select essential log fields
940 /// let field_selection = FieldSelection::new()
941 /// .log([LogField::Address, LogField::Data, LogField::Topic0]);
942 ///
943 /// // Select all log fields for comprehensive event data
944 /// let field_selection = FieldSelection::new()
945 /// .log(LogField::all());
946 ///
947 /// // Select all topic fields for event analysis
948 /// let field_selection = FieldSelection::new()
949 /// .log([
950 /// LogField::Topic0,
951 /// LogField::Topic1,
952 /// LogField::Topic2,
953 /// LogField::Topic3,
954 /// ]);
955 ///
956 /// // Chain with transaction fields - mix all and specific
957 /// let field_selection = FieldSelection::new()
958 /// .transaction([TransactionField::Hash])
959 /// .log(LogField::all());
960 /// ```
961 pub fn log<T: IntoIterator<Item = LogField>>(mut self, fields: T) -> Self {
962 self.log.extend(fields);
963 self
964 }
965 /// Select specific trace fields to include in query results.
966 ///
967 /// This method allows you to specify which trace fields should be returned in the query response.
968 /// Only the selected fields will be included, which can improve performance and reduce payload size.
969 ///
970 /// # Availability
971 /// **Note**: Trace data is only available on select hypersync instances. Not all blockchain
972 /// networks provide trace data, and it requires additional infrastructure to collect and serve.
973 /// Check your hypersync instance documentation to confirm trace availability for your target network.
974 ///
975 /// # Arguments
976 /// * `fields` - An iterable of `TraceField` values to select
977 ///
978 /// # Examples
979 ///
980 /// ```
981 /// use hypersync_net_types::{
982 /// FieldSelection,
983 /// trace::TraceField,
984 /// transaction::TransactionField,
985 /// log::LogField
986 /// };
987 ///
988 /// // Select basic trace information
989 /// let field_selection = FieldSelection::new()
990 /// .trace([TraceField::From, TraceField::To, TraceField::Value]);
991 ///
992 /// // Select all trace fields for comprehensive trace analysis
993 /// let field_selection = FieldSelection::new()
994 /// .trace(TraceField::all());
995 ///
996 /// // Select trace execution details
997 /// let field_selection = FieldSelection::new()
998 /// .trace([
999 /// TraceField::CallType,
1000 /// TraceField::Input,
1001 /// TraceField::Output,
1002 /// TraceField::Gas,
1003 /// TraceField::GasUsed,
1004 /// ]);
1005 ///
1006 /// // Combine with other data types - mix all and specific
1007 /// let field_selection = FieldSelection::new()
1008 /// .transaction([TransactionField::Hash])
1009 /// .trace(TraceField::all())
1010 /// .log([LogField::Address]);
1011 /// ```
1012 pub fn trace<T: IntoIterator<Item = TraceField>>(mut self, fields: T) -> Self {
1013 self.trace.extend(fields);
1014 self
1015 }
1016}
1017
1018impl query_body::Builder<'_> {
1019 pub fn build_from_query(&mut self, query: &Query) -> Result<(), capnp::Error> {
1020 self.reborrow()
1021 .set_include_all_blocks(query.include_all_blocks);
1022
1023 // Set max nums using OptUInt64
1024 if let Some(max_num_blocks) = query.max_num_blocks {
1025 let mut max_blocks_builder = self.reborrow().init_max_num_blocks();
1026 max_blocks_builder.set_value(max_num_blocks as u64);
1027 }
1028 if let Some(max_num_transactions) = query.max_num_transactions {
1029 let mut max_tx_builder = self.reborrow().init_max_num_transactions();
1030 max_tx_builder.set_value(max_num_transactions as u64);
1031 }
1032 if let Some(max_num_logs) = query.max_num_logs {
1033 let mut max_logs_builder = self.reborrow().init_max_num_logs();
1034 max_logs_builder.set_value(max_num_logs as u64);
1035 }
1036 if let Some(max_num_traces) = query.max_num_traces {
1037 let mut max_traces_builder = self.reborrow().init_max_num_traces();
1038 max_traces_builder.set_value(max_num_traces as u64);
1039 }
1040
1041 // Set join mode
1042 let join_mode = match query.join_mode {
1043 JoinMode::Default => hypersync_net_types_capnp::JoinMode::Default,
1044 JoinMode::JoinAll => hypersync_net_types_capnp::JoinMode::JoinAll,
1045 JoinMode::JoinNothing => hypersync_net_types_capnp::JoinMode::JoinNothing,
1046 };
1047 self.reborrow().set_join_mode(join_mode);
1048
1049 // Set field selection
1050 {
1051 let mut field_selection = self.reborrow().init_field_selection();
1052 query
1053 .field_selection
1054 .populate_builder(&mut field_selection)?;
1055 }
1056
1057 // Set logs
1058 {
1059 let mut logs_list = self.reborrow().init_logs(query.logs.len() as u32);
1060 for (i, log_selection) in query.logs.iter().enumerate() {
1061 let mut log_sel = logs_list.reborrow().get(i as u32);
1062 log_selection.populate_builder(&mut log_sel)?;
1063 }
1064 }
1065
1066 // Set transactions
1067 {
1068 let mut tx_list = self
1069 .reborrow()
1070 .init_transactions(query.transactions.len() as u32);
1071 for (i, tx_selection) in query.transactions.iter().enumerate() {
1072 let mut tx_sel = tx_list.reborrow().get(i as u32);
1073 tx_selection.populate_builder(&mut tx_sel)?;
1074 }
1075 }
1076
1077 // Set traces
1078 {
1079 let mut trace_list = self.reborrow().init_traces(query.traces.len() as u32);
1080 for (i, trace_selection) in query.traces.iter().enumerate() {
1081 let mut trace_sel = trace_list.reborrow().get(i as u32);
1082 trace_selection.populate_builder(&mut trace_sel)?;
1083 }
1084 }
1085
1086 // Set blocks
1087 {
1088 let mut block_list = self.reborrow().init_blocks(query.blocks.len() as u32);
1089 for (i, block_selection) in query.blocks.iter().enumerate() {
1090 let mut block_sel = block_list.reborrow().get(i as u32);
1091 block_selection.populate_builder(&mut block_sel)?;
1092 }
1093 }
1094 Ok(())
1095 }
1096}
1097
1098impl CapnpReader<hypersync_net_types_capnp::request::Owned> for Query {
1099 fn from_reader(
1100 query: hypersync_net_types_capnp::request::Reader,
1101 ) -> Result<Self, capnp::Error> {
1102 let block_range = query.get_block_range()?;
1103 let from_block = block_range.get_from_block();
1104 let to_block = if block_range.has_to_block() {
1105 Some(block_range.get_to_block()?.get_value())
1106 } else {
1107 None
1108 };
1109 let body_reader = match query.get_body().which()? {
1110 request::body::Which::Query(query_body_reader) => query_body_reader?,
1111 request::body::Which::QueryId(_) => {
1112 return Err(capnp::Error::failed(
1113 "QueryId cannot be read from capnp request with QueryBody".to_string(),
1114 ));
1115 }
1116 };
1117
1118 Query::from_capnp_query_body_reader(&body_reader, from_block, to_block)
1119 }
1120}
1121impl CapnpBuilder<hypersync_net_types_capnp::field_selection::Owned> for FieldSelection {
1122 fn populate_builder(
1123 &self,
1124 field_selection: &mut hypersync_net_types_capnp::field_selection::Builder,
1125 ) -> Result<(), capnp::Error> {
1126 // Set block fields
1127 if !self.block.is_empty() {
1128 let mut block_list = field_selection
1129 .reborrow()
1130 .init_block(self.block.len() as u32);
1131 for (i, field) in self.block.iter().enumerate() {
1132 block_list.set(i as u32, field.to_capnp());
1133 }
1134 }
1135
1136 if !self.transaction.is_empty() {
1137 // Set transaction fields
1138 let mut tx_list = field_selection
1139 .reborrow()
1140 .init_transaction(self.transaction.len() as u32);
1141 for (i, field) in self.transaction.iter().enumerate() {
1142 tx_list.set(i as u32, field.to_capnp());
1143 }
1144 }
1145
1146 if !self.log.is_empty() {
1147 // Set log fields
1148 let mut log_list = field_selection.reborrow().init_log(self.log.len() as u32);
1149 for (i, field) in self.log.iter().enumerate() {
1150 log_list.set(i as u32, field.to_capnp());
1151 }
1152 }
1153
1154 if !self.trace.is_empty() {
1155 // Set trace fields
1156 let mut trace_list = field_selection
1157 .reborrow()
1158 .init_trace(self.trace.len() as u32);
1159 for (i, field) in self.trace.iter().enumerate() {
1160 trace_list.set(i as u32, field.to_capnp());
1161 }
1162 }
1163
1164 Ok(())
1165 }
1166}
1167
1168impl CapnpReader<hypersync_net_types_capnp::field_selection::Owned> for FieldSelection {
1169 fn from_reader(
1170 fs: hypersync_net_types_capnp::field_selection::Reader,
1171 ) -> Result<Self, capnp::Error> {
1172 let mut block_fields = BTreeSet::new();
1173 if fs.has_block() {
1174 let block_list = fs.get_block()?;
1175 for block in block_list {
1176 block_fields.insert(BlockField::from_capnp(block?));
1177 }
1178 }
1179
1180 let mut transaction_fields = BTreeSet::new();
1181 if fs.has_transaction() {
1182 let tx_list = fs.get_transaction()?;
1183 for tx in tx_list {
1184 transaction_fields.insert(TransactionField::from_capnp(tx?));
1185 }
1186 }
1187
1188 let mut log_fields = BTreeSet::new();
1189 if fs.has_log() {
1190 let log_list = fs.get_log()?;
1191 for log in log_list {
1192 log_fields.insert(LogField::from_capnp(log?));
1193 }
1194 }
1195
1196 let mut trace_fields = BTreeSet::new();
1197 if fs.has_trace() {
1198 let trace_list = fs.get_trace()?;
1199 for trace in trace_list {
1200 trace_fields.insert(TraceField::from_capnp(trace?));
1201 }
1202 };
1203
1204 Ok(FieldSelection {
1205 block: block_fields,
1206 transaction: transaction_fields,
1207 log: log_fields,
1208 trace: trace_fields,
1209 })
1210 }
1211}
1212
1213#[cfg(test)]
1214pub mod tests {
1215 use super::*;
1216 use capnp::message::{Builder, ReaderOptions};
1217 use pretty_assertions::assert_eq;
1218 use serde_json::json;
1219
1220 pub fn test_query_serde(query: Query, label: &str) {
1221 fn test_encode_decode<T: PartialEq + std::fmt::Debug>(
1222 input: &T,
1223 label: String,
1224 encode: impl FnOnce(&T) -> Vec<u8>,
1225 decode: impl FnOnce(&[u8]) -> T,
1226 ) {
1227 let val = encode(input);
1228 let decoded = decode(&val);
1229 assert_eq!(input, &decoded, "{label} does not match");
1230 }
1231
1232 fn to_capnp_bytes(query: &Query) -> Vec<u8> {
1233 let mut message = Builder::new_default();
1234 let mut query_builder =
1235 message.init_root::<hypersync_net_types_capnp::request::Builder>();
1236
1237 query_builder
1238 .build_full_query_from_query(query, false)
1239 .unwrap();
1240
1241 let mut buf = Vec::new();
1242 capnp::serialize::write_message(&mut buf, &message).unwrap();
1243 buf
1244 }
1245
1246 fn from_capnp_bytes(bytes: &[u8]) -> Query {
1247 let message_reader = capnp::serialize::read_message(
1248 &mut std::io::Cursor::new(bytes),
1249 ReaderOptions::new(),
1250 )
1251 .unwrap();
1252 let query = message_reader
1253 .get_root::<hypersync_net_types_capnp::request::Reader>()
1254 .unwrap();
1255
1256 Query::from_reader(query).unwrap()
1257 }
1258
1259 test_encode_decode(
1260 &query,
1261 label.to_string() + "-capnp",
1262 to_capnp_bytes,
1263 from_capnp_bytes,
1264 );
1265 test_encode_decode(
1266 &query,
1267 label.to_string() + "-json",
1268 |q| serde_json::to_vec(q).unwrap(),
1269 |bytes| serde_json::from_slice(bytes).unwrap(),
1270 );
1271 }
1272
1273 #[test]
1274 pub fn test_query_serde_default() {
1275 let query = Query::default();
1276 test_query_serde(query, "default");
1277 }
1278
1279 #[test]
1280 pub fn test_query_serde_with_non_null_defaults() {
1281 let query = Query {
1282 from_block: u64::default(),
1283 to_block: Some(u64::default()),
1284 logs: Vec::default(),
1285 transactions: Vec::default(),
1286 traces: Vec::default(),
1287 blocks: Vec::default(),
1288 include_all_blocks: bool::default(),
1289 field_selection: FieldSelection::default(),
1290 max_num_blocks: Some(usize::default()),
1291 max_num_transactions: Some(usize::default()),
1292 max_num_logs: Some(usize::default()),
1293 max_num_traces: Some(usize::default()),
1294 join_mode: JoinMode::default(),
1295 };
1296 test_query_serde(query, "base query with_non_null_defaults");
1297 }
1298
1299 #[test]
1300 pub fn test_query_serde_with_non_null_values() {
1301 let query = Query {
1302 from_block: 50,
1303 to_block: Some(500),
1304 logs: Vec::default(),
1305 transactions: Vec::default(),
1306 traces: Vec::default(),
1307 blocks: Vec::default(),
1308 include_all_blocks: true,
1309 field_selection: FieldSelection::default(),
1310 max_num_blocks: Some(50),
1311 max_num_transactions: Some(100),
1312 max_num_logs: Some(150),
1313 max_num_traces: Some(200),
1314 join_mode: JoinMode::JoinAll,
1315 };
1316 test_query_serde(query, "base query with_non_null_values");
1317 }
1318
1319 #[test]
1320 pub fn test_query_serde_with_empty_topics() {
1321 let query_json = json!({
1322 "from_block": 0,
1323 "logs": [
1324 {
1325 "address": ["0x000000000000aDdB49795b0f9bA5BC298cDda236"],
1326 "topics": [
1327 [
1328 "0x2150ada912bf189ed721c44211199e270903fc88008c2a1e1e889ef30fe67c5f",
1329 ],
1330 [],
1331 [],
1332 [],
1333 ],
1334 }
1335 ]
1336 });
1337 let query: Query = serde_json::from_value(query_json).unwrap();
1338
1339 let mut message = Builder::new_default();
1340 let mut request_builder =
1341 message.init_root::<hypersync_net_types_capnp::request::Builder>();
1342 request_builder
1343 .build_full_query_from_query(&query, true)
1344 .unwrap();
1345
1346 let read_query = Query::from_reader(request_builder.into_reader()).unwrap();
1347 assert_eq!(query, read_query);
1348 }
1349}