hypersync_net_types/
query.rs

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