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}