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