Skip to content

Conversation

@JonoPrest
Copy link
Collaborator

@JonoPrest JonoPrest commented Nov 6, 2025

Implement a new builder API for queries

Summary by CodeRabbit

  • New Features

    • Fluent builder-based query API with typed field selection and chainable filters.
    • Stable query identifiers and an optional caching flag.
    • TryFrom conversions for fixed-size data from strings.
  • Improvements

    • Examples migrated from JSON fixtures to the new builder API.
    • Broad error-propagation improvements for more robust runtime behavior.
  • Refactor

    • Wire format moved to a request-centric shape while preserving compatibility.
  • Tests

    • Added coverage for filter builders and query-id hashing.
  • Chores

    • Added runtime error-handling dependency to examples.

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Walkthrough

Replaces JSON query literals with a typed, fluent Query/FieldSelection builder across examples; renames Cap’n Proto root from Query → Request (adds queryId and shouldCache) and updates client wiring to build request payloads; adds builder-style filters and TryFrom for FixedSizeData.

Changes

Cohort / File(s) Summary of Changes
Examples: add anyhow dependency
examples/all_erc20/Cargo.toml, examples/call_watch/Cargo.toml, examples/reverse_wallet/Cargo.toml, examples/wallet/Cargo.toml, examples/watch/Cargo.toml
Adds anyhow to example manifests; minor manifest normalization.
Examples: migrate to typed Query builder + Result-returning main
examples/all_erc20/src/main.rs, examples/call_decode_output/src/main.rs, examples/call_watch/src/main.rs, examples/reverse_wallet/src/main.rs, examples/wallet/src/main.rs, examples/watch/src/main.rs
Replace serde_json queries with Query::new() fluent API and typed filter/field builders (Log/Transaction/Trace); change async fn main()async fn main() -> anyhow::Result<()> and propagate errors.
Cap’n Proto schema/root rename and bench updates
hypersync-net-types/hypersync_net_types.capnp, hypersync-net-types/benches/compression.rs
Rename root struct to Request (explicit ID kept); add queryId union member and shouldCache flag; update bench encoding/decoding to use request root.
Client: request-based payload building
hypersync-client/src/lib.rs
Use request::Builder for capnp payloads in both full-query and id paths; remove x-hypersync-cache-queries header usage.
Net types: Query API overhaul + Request/QueryId
hypersync-net-types/src/query.rs, hypersync-net-types/src/lib.rs, hypersync-net-types/src/request.rs
Introduce fluent Query and FieldSelection API, request module, Request enum and QueryId helpers; update CapnpReader/CapnpBuilder to operate with request/query_body.
Filter builders: logs, transactions, traces, blocks
hypersync-net-types/src/log.rs, hypersync-net-types/src/transaction.rs, hypersync-net-types/src/trace.rs, hypersync-net-types/src/block.rs
Add any() and and_* chainable builder methods with typed conversions and contextual anyhow errors; update tests to use builders; add topic/slot handling and per-item error contexts.
Utility conversions
hypersync-format/src/types/fixed_size_data.rs
Add TryFrom<&str> and TryFrom<String> for FixedSizeData<N> delegating to FromStr.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Examples as examples/*/main.rs
  participant NetTypes as net_types (Query/Request)
  participant Client as hypersync-client
  participant Server as Hypersync Service

  App->>Examples: Build Query via fluent API
  Examples->>NetTypes: Query::select_*(), .where_*(...), .from_block(...)
  Examples->>Client: client.get(Request or Query)
  Client->>NetTypes: request::Builder::build_full_query_from_query(...) or build_query_id_from_query(...)
  Client->>Server: Send Request { body: query | queryId, shouldCache?, blockRange }
  Server-->>Client: Response (results)
  Client-->>App: Streamed results
Loading
sequenceDiagram
  autonumber
  participant NetTypes as net_types::Request
  participant Client as hypersync-client
  participant Server as Hypersync Service

  NetTypes->>Client: QueryId creation (canonicalize & hash)
  Client->>Server: Send Request { queryId, blockRange }
  Server-->>Client: Cached response path
  Client-->>NetTypes: Deserialize result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • hypersync-net-types/src/query.rs and src/request.rs (capnp builders/readers, canonicalization and hashing)
    • hypersync-net-types/src/log.rs, trace.rs, transaction.rs, block.rs (builder semantics and per-item conversion error contexts)
    • hypersync-client/src/lib.rs (request payload wiring and removed header)
    • Cap’n Proto schema hypersync_net_types.capnp and bench adjustments

Possibly related PRs

Suggested reviewers

  • JasoonS

Poem

I nibble bytes with whiskered cheer,
From JSON burrows to builder clear.
Request hops in where Query played,
Topics, hashes—neatly arrayed.
Ok(()), I bound — new filters bloom, hooray! 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement Query Builder API with Docs' accurately captures the main objective of this PR, which introduces a fluent builder API for constructing queries throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jp/query-builder-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@JonoPrest JonoPrest force-pushed the jp/query-builder-api branch from e5d1a95 to da5d20a Compare November 7, 2025 11:15
@JonoPrest JonoPrest changed the title Jp/query builder api Implement Query Builder API with Docs Nov 7, 2025
@JonoPrest JonoPrest marked this pull request as ready for review November 7, 2025 14:14
@JonoPrest JonoPrest requested review from DZakh and JasoonS November 7, 2025 14:14
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
hypersync-net-types/src/query.rs (2)

173-177: Typo in documentation: “Weather” → “Whether”.

Fix the comment on include_all_blocks.


589-616: u64 ⇄ usize conversions can truncate on 32-bit; make fallible.

Reading from capnp uses as usize; this can truncate values > usize::MAX on 32‑bit targets. Prefer fallible conversion and error out. Example patch (applies similarly to tx/log/trace counts):

-            let value = max_blocks_reader.get_value();
-            Some(value as usize)
+            let value = max_blocks_reader.get_value();
+            let v = usize::try_from(value)
+                .map_err(|_| capnp::Error::failed("max_num_blocks exceeds usize".to_string()))?;
+            Some(v)

Writing is fine (usizeu64), but stays symmetric after the read fix.

Also applies to: 882-897

examples/call_watch/src/main.rs (1)

47-60: Correct the transfer log text.

Typo: the message prints “DAU transfer” instead of “DAI transfer,” which is confusing for users reading the output.

♻️ Duplicate comments (2)
examples/wallet/Cargo.toml (1)

13-13: Same version‑skew note as call_watch

Align anyhow version via workspace to match other examples (e.g., all_erc20). Keeps builds lean.

examples/reverse_wallet/Cargo.toml (1)

13-13: Consolidate anyhow version

Pin through [workspace.dependencies] and reference here with { workspace = true } to avoid multiple anyhow versions.

🧹 Nitpick comments (10)
examples/watch/Cargo.toml (1)

13-13: Confirmed usage; inconsistent anyhow versions across examples—refactor recommended.

Verification shows anyhow is actively used in examples/watch/src/main.rs (returns anyhow::Result<()>). However, anyhow versions are inconsistent: five examples pin 1.0.89, but examples/all_erc20 pins 1.0.100. While patch-level differences are compatible, this inconsistency is a maintenance concern.

Recommended refactor:

  • Align all examples to the same version, or
  • Switch to a caret range (anyhow = "1") for automatic patch upgrades, or
  • Promote anyhow to [workspace.dependencies] and use anyhow.workspace = true across all examples for consistency.
hypersync-format/src/types/fixed_size_data.rs (1)

131-137: Ergonomic addition; minor idiomatic polish + add tests for TryFrom paths

The impls are correct and consistent with FromStr. Optionally tighten style by using s.parse::<Self>() (clearer, no UFCS), and add unit tests covering both &str and String conversions.

Apply this small polish:

 impl<const N: usize> TryFrom<&str> for FixedSizeData<N> {
     type Error = Error;

     fn try_from(s: &str) -> StdResult<Self, Self::Error> {
-        std::str::FromStr::from_str(s)
+        s.parse::<Self>()
     }
 }

 impl<const N: usize> TryFrom<String> for FixedSizeData<N> {
     type Error = Error;

     fn try_from(s: String) -> StdResult<Self, Self::Error> {
-        std::str::FromStr::from_str(&s)
+        s.parse::<Self>()
     }
 }

Add focused tests:

@@
     fn test_display() {
         let data = FSD4::from(hex!("42feed00"));
         // Check that Display prints the 0x-prefixed hex
         assert_eq!(data.to_string(), "0x42feed00");
     }
+
+    #[test]
+    fn test_try_from_str_valid() {
+        let data = FSD4::try_from("0x00420000").expect("valid 4-byte hex via &str");
+        assert_eq!(data, FSD4::from(hex!("00420000")));
+    }
+
+    #[test]
+    fn test_try_from_string_valid() {
+        let s = String::from("0x42000000");
+        let data = FSD4::try_from(s).expect("valid 4-byte hex via String");
+        assert_eq!(data, FSD4::from(hex!("42000000")));
+    }
+
+    #[test]
+    fn test_try_from_str_missing_prefix_err() {
+        let data = FSD4::try_from("00420000");
+        assert!(data.is_err());
+    }
 }

Please run the test suite to confirm the new cases pass and no trait coherence warnings appear.

Also applies to: 139-145

examples/all_erc20/Cargo.toml (1)

14-15: Normalize anyhow across examples

This crate uses anyhow = "1.0.100" while others use 1.0.89. Suggest centralizing in [workspace.dependencies] and referencing from each example.

I can draft the workspace stanza if you want.

hypersync-client/src/lib.rs (1)

507-509: Nit: remove duplicate content-type header

You set "content-type: application/x-capnp" twice in this function (earlier and again before .body(full_query_bytes)). Drop the second call.

Apply:

-        let res = req
-            .header("content-type", "application/x-capnp")
+        let res = req
             .body(full_query_bytes)
             .send()
hypersync-net-types/src/block.rs (1)

21-143: Nice builder API with contextual errors

any(), and_hash_any, and and_miner_address_any are clear and consistent with other filters. Docs are helpful.

Optional: add single‑value helpers (and_hash, and_miner_address) that forward to the *_any forms for common cases.

examples/call_watch/Cargo.toml (1)

13-13: Unify anyhow under workspace.dependencies

Add to your root Cargo.toml:

[workspace.dependencies]
anyhow = "1.0.100"

Then in each member’s Cargo.toml replace any anyhow = "…" (e.g. "1.0.89", "1.0.100" or "1") with:

anyhow = { workspace = true }

This ensures a single anyhow version and avoids duplicate builds.

hypersync-net-types/src/query.rs (4)

280-289: Builder semantics: match_*_any replace previous selections.

Current methods overwrite existing vectors, which may surprise users chaining multiple calls. Either:

  • Document that they replace prior selections, or
  • Change to extend existing selections (or add add_*_any variants that append).

Happy to provide a patch for either approach.

Also applies to: 323-332, 369-378, 421-429


907-914: Avoid initializing empty capnp lists/structs; align with serde “skip if default”.

  • You always init_field_selection() even when empty; and call init_* with length 0 for lists. This sets non-null pointers, so has_*() becomes true though logically empty.
  • Guard inits to reduce payload and keep presence semantics clean.

Example refactor:

-        {
-            let mut field_selection = self.reborrow().init_field_selection();
-            query.field_selection.populate_builder(&mut field_selection)?;
-        }
+        if !(query.field_selection.block.is_empty()
+            && query.field_selection.transaction.is_empty()
+            && query.field_selection.log.is_empty()
+            && query.field_selection.trace.is_empty())
+        {
+            let mut fs = self.reborrow().init_field_selection();
+            query.field_selection.populate_builder(&mut fs)?;
+        }

-        {
-            let mut logs_list = self.reborrow().init_logs(query.logs.len() as u32);
+        if !query.logs.is_empty() {
+            let mut logs_list = self.reborrow().init_logs(query.logs.len() as u32);
             ...
-        }
+        }

Apply similarly to transactions, traces, and blocks.

Also applies to: 917-952


969-974: Clarify error message when body is QueryId.

Message reads as if QueryId “cannot be read … with QueryBody”. Suggest:

- "QueryId cannot be read from capnp request with QueryBody"
+ "expected request.body=Query, got QueryId"

Small polish for debuggability.


409-416: Trace call type literals: please verify accepted values.

Example uses "create" and "suicide". Ensure these match your TraceCallType (or equivalent) canonical names; many APIs use create / selfdestruct. Update docs for consistency if needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40e7455 and 1168564.

⛔ Files ignored due to path filters (1)
  • hypersync-net-types/src/__generated__/hypersync_net_types_capnp.rs is excluded by !**/__generated__/**
📒 Files selected for processing (22)
  • examples/all_erc20/Cargo.toml (1 hunks)
  • examples/all_erc20/src/main.rs (4 hunks)
  • examples/call_decode_output/src/main.rs (2 hunks)
  • examples/call_watch/Cargo.toml (1 hunks)
  • examples/call_watch/src/main.rs (1 hunks)
  • examples/reverse_wallet/Cargo.toml (1 hunks)
  • examples/reverse_wallet/src/main.rs (2 hunks)
  • examples/wallet/Cargo.toml (1 hunks)
  • examples/wallet/src/main.rs (4 hunks)
  • examples/watch/Cargo.toml (1 hunks)
  • examples/watch/src/main.rs (2 hunks)
  • hypersync-client/src/lib.rs (2 hunks)
  • hypersync-format/src/types/fixed_size_data.rs (1 hunks)
  • hypersync-net-types/benches/compression.rs (3 hunks)
  • hypersync-net-types/hypersync_net_types.capnp (1 hunks)
  • hypersync-net-types/src/block.rs (2 hunks)
  • hypersync-net-types/src/lib.rs (3 hunks)
  • hypersync-net-types/src/log.rs (3 hunks)
  • hypersync-net-types/src/query.rs (6 hunks)
  • hypersync-net-types/src/request.rs (1 hunks)
  • hypersync-net-types/src/trace.rs (2 hunks)
  • hypersync-net-types/src/transaction.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (13)
hypersync-net-types/src/block.rs (3)
hypersync-net-types/src/log.rs (1)
  • any (25-27)
hypersync-net-types/src/transaction.rs (3)
  • any (21-23)
  • any (168-170)
  • and_hash_any (475-490)
hypersync-net-types/src/trace.rs (1)
  • any (52-54)
hypersync-net-types/src/log.rs (2)
hypersync-net-types/src/transaction.rs (3)
  • any (21-23)
  • any (168-170)
  • and_address_any (98-114)
hypersync-net-types/src/trace.rs (2)
  • any (52-54)
  • and_address_any (195-211)
hypersync-net-types/src/request.rs (2)
hypersync-net-types/src/query.rs (8)
  • new (228-230)
  • new (708-710)
  • from_reader (957-977)
  • from_reader (1027-1068)
  • from_block (232-235)
  • to_block (237-240)
  • from_capnp_query_body_reader (573-673)
  • log (819-822)
hypersync-net-types/src/lib.rs (4)
  • from (98-103)
  • new (64-69)
  • from_reader (111-113)
  • from_reader (144-154)
examples/call_watch/src/main.rs (6)
hypersync-net-types/src/transaction.rs (1)
  • TransactionField (1188-1191)
examples/call_decode_output/src/main.rs (1)
  • main (14-85)
examples/reverse_wallet/src/main.rs (1)
  • main (12-86)
examples/wallet/src/main.rs (1)
  • main (18-128)
examples/watch/src/main.rs (1)
  • main (13-80)
hypersync-client/src/decode_call.rs (1)
  • from_signatures (27-40)
hypersync-net-types/src/transaction.rs (3)
hypersync-net-types/src/log.rs (2)
  • any (25-27)
  • and_address_any (66-82)
hypersync-net-types/src/block.rs (2)
  • any (26-28)
  • and_hash_any (67-82)
hypersync-net-types/src/trace.rs (6)
  • any (52-54)
  • and_address_any (195-211)
  • and_from_address_any (94-110)
  • and_to_address_any (147-163)
  • and_sighash_any (345-361)
  • and_type_any (300-307)
examples/all_erc20/src/main.rs (2)
hypersync-net-types/src/query.rs (3)
  • log (819-822)
  • new (228-230)
  • new (708-710)
hypersync-net-types/src/log.rs (2)
  • LogField (491-494)
  • any (25-27)
examples/watch/src/main.rs (3)
hypersync-net-types/src/log.rs (1)
  • LogField (491-494)
examples/wallet/src/main.rs (1)
  • main (18-128)
examples/all_erc20/src/main.rs (1)
  • main (17-113)
examples/wallet/src/main.rs (3)
examples/call_watch/src/main.rs (1)
  • main (13-82)
examples/watch/src/main.rs (1)
  • main (13-80)
examples/all_erc20/src/main.rs (1)
  • main (17-113)
examples/reverse_wallet/src/main.rs (3)
examples/call_decode_output/src/main.rs (1)
  • main (14-85)
examples/call_watch/src/main.rs (1)
  • main (13-82)
examples/wallet/src/main.rs (1)
  • main (18-128)
hypersync-net-types/src/trace.rs (2)
hypersync-net-types/src/log.rs (2)
  • any (25-27)
  • and_address_any (66-82)
hypersync-net-types/src/transaction.rs (7)
  • any (21-23)
  • any (168-170)
  • and_from_address_any (209-225)
  • and_to_address_any (261-277)
  • and_address_any (98-114)
  • and_type_any (383-389)
  • and_sighash_any (314-330)
hypersync-net-types/src/lib.rs (5)
hypersync-net-types/src/query.rs (4)
  • block (742-745)
  • log (819-822)
  • trace (870-873)
  • transaction (780-783)
hypersync-net-types/src/block.rs (1)
  • BlockField (391-394)
hypersync-net-types/src/log.rs (1)
  • LogField (491-494)
hypersync-net-types/src/trace.rs (1)
  • TraceField (756-759)
hypersync-net-types/src/transaction.rs (1)
  • TransactionField (1188-1191)
examples/call_decode_output/src/main.rs (2)
hypersync-net-types/src/trace.rs (1)
  • TraceField (756-759)
hypersync-net-types/src/lib.rs (1)
  • new (64-69)
hypersync-net-types/src/query.rs (2)
hypersync-net-types/src/lib.rs (3)
  • new (64-69)
  • from_reader (111-113)
  • from_reader (144-154)
hypersync-net-types/src/request.rs (1)
  • from_reader (71-103)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test_release
  • GitHub Check: test_dev
🔇 Additional comments (4)
hypersync-client/src/lib.rs (1)

448-452: Cap’n Proto switch to request root looks correct

Using request::Builder and build_query_id_from_query(query)? is consistent with the new schema. Confirm server semantics: for the queryId path you don’t set shouldCache; for the full query you pass it via build_full_query_from_query(query, should_cache). If the server expects shouldCache only on full queries, this is fine.

hypersync-net-types/benches/compression.rs (1)

330-330: Root switch to request::{Builder,Reader} is consistent

Encoding/decoding now uses the new Request root and continues to round-trip via Query::from_reader(...). Looks good.

Also applies to: 351-352, 360-361

hypersync-net-types/hypersync_net_types.capnp (1)

239-250: Confirm struct ID continuity and add wire-compat test

Renaming QueryRequest with explicit @0xbe854ee336b40054 preserves on-wire compatibility; please add a CI test that decodes legacy Query bytes into Request::Reader to validate the ID matches.

hypersync-net-types/src/query.rs (1)

688-874: FieldSelection builder API: solid, deterministic, and ergonomic.

Using BTreeSet ensures deterministic ordering; chaining methods read well. Nice work.

Comment on lines 23 to 28
.match_transactions_any([
// The logs we want. We will also automatically get transactions and blocks relating to these logs (the query implicitly joins them).
// We want all DAI transfers so no address filter and only a filter for the first topic
hypersync_client::net_types::TransactionFilter::any()
.and_from_address_any([DAI_ADDRESS])?
.and_to_address_any([DAI_ADDRESS])?,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the DAI transaction filter logic.

Requiring both the from and to address to equal the DAI contract means only self-calls from the contract survive, so regular transfer/transferFrom calls (user → DAI) are filtered out. The stream will appear empty. Drop the from constraint (or replace it with the intended caller set) so you still capture real transfers.

-        .match_transactions_any([
-            hypersync_client::net_types::TransactionFilter::any()
-                .and_from_address_any([DAI_ADDRESS])?
-                .and_to_address_any([DAI_ADDRESS])?,
-        ])
+        .match_transactions_any([
+            hypersync_client::net_types::TransactionFilter::any()
+                .and_to_address_any([DAI_ADDRESS])?,
+        ])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.match_transactions_any([
// The logs we want. We will also automatically get transactions and blocks relating to these logs (the query implicitly joins them).
// We want all DAI transfers so no address filter and only a filter for the first topic
hypersync_client::net_types::TransactionFilter::any()
.and_from_address_any([DAI_ADDRESS])?
.and_to_address_any([DAI_ADDRESS])?,
.match_transactions_any([
// The logs we want. We will also automatically get transactions and blocks relating to these logs (the query implicitly joins them).
// We want all DAI transfers so no address filter and only a filter for the first topic
hypersync_client::net_types::TransactionFilter::any()
.and_to_address_any([DAI_ADDRESS])?,
])
🤖 Prompt for AI Agents
In examples/call_watch/src/main.rs around lines 23 to 28, the transaction filter
currently requires both from and to to equal the DAI contract which only matches
self-calls; remove the from-address constraint so normal transfers (user → DAI)
are captured. Specifically, delete the .and_from_address_any([DAI_ADDRESS])?
call (or replace it with the intended caller whitelist if you meant to restrict
callers), leaving the .and_to_address_any([DAI_ADDRESS])? in place and ensuring
the chain of builder calls and error handling remain valid.

Comment on lines 29 to 31
.match_transactions_any([TransactionFilter::any()
.and_from_address_any([address])?
.and_to_address_any([address])?]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the transaction filter OR semantics.

Chaining .and_from_address_any([address])? and .and_to_address_any([address])? on the same TransactionFilter now requires every transaction to have both sender and recipient equal to address, which effectively filters out the transfers this example is supposed to surface. Please keep these predicates in separate filters so match_transactions_any retains its intended OR semantics.

-        .match_transactions_any([TransactionFilter::any()
-            .and_from_address_any([address])?
-            .and_to_address_any([address])?]);
+        .match_transactions_any([
+            TransactionFilter::any().and_from_address_any([address])?,
+            TransactionFilter::any().and_to_address_any([address])?,
+        ]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.match_transactions_any([TransactionFilter::any()
.and_from_address_any([address])?
.and_to_address_any([address])?]);
.match_transactions_any([
TransactionFilter::any().and_from_address_any([address])?,
TransactionFilter::any().and_to_address_any([address])?,
]);
🤖 Prompt for AI Agents
In examples/reverse_wallet/src/main.rs around lines 29 to 31, the code chains
.and_from_address_any([address])? and .and_to_address_any([address])? on the
same TransactionFilter which produces an AND (both sender and recipient must
match) instead of the intended OR; fix this by creating two separate
TransactionFilter instances (one with .and_from_address_any([address])? and
another with .and_to_address_any([address])?) and pass them as separate entries
to .match_transactions_any([...]) so the filters are evaluated with OR
semantics.

Comment on lines 32 to 55
/// ```
/// use hypersync_net_types::{
/// Query, FieldSelection, LogFilter, BlockFilter, TransactionFilter,
/// block::BlockField, log::LogField, transaction::TransactionField
/// };
///
/// // Simple log query for USDT transfers
/// let usdt_transfers = Query::new()
/// .from_block(18_000_000)
/// .to_block(18_001_000)
/// .select_fields(
/// FieldSelection::new()
/// .block([BlockField::Number, BlockField::Timestamp])
/// .log([LogField::Address, LogField::Data, LogField::Topic0, LogField::Topic1, LogField::Topic2])
/// .transaction([TransactionField::Hash, TransactionField::From, TransactionField::To])
/// )
/// .match_logs_any([
/// LogFilter::any()
/// .and_address_any(["0xdac17f958d2ee523a2206206994597c13d831ec7"])? // USDT contract
/// .and_topic0_any(["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"])? // Transfer event
/// ]);
/// # Ok::<(), anyhow::Error>(())
/// ```
///
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Doctests will fail due to ? in code blocks; also one example mislabeled.

  • Most examples use ? but aren’t wrapped in a fn main() -> anyhow::Result<()> { ... } (hidden lines) or marked no_run/ignore. Rustdoc will compile these and fail. Wrap each example with hidden Result-returning main or add rust,no_run to the fence.
  • In the “Include all blocks” section, the snippet labeled “Normal mode - only blocks with matching logs” calls .include_all_blocks(), which contradicts the comment. Remove that call or fix the label.

I can patch the fences (hidden wrapper) across these blocks if you want.

Also applies to: 75-102, 117-131, 254-279, 301-323, 345-369, 396-420, 545-567


🏁 Script executed:

#!/bin/bash
# Print the first 100 lines to inspect doc examples around lines 32-55
sed -n '1,100p' hypersync-net-types/src/query.rs

Length of output: 4567


🏁 Script executed:

#!/bin/bash
# Print lines 75-140 to inspect the Include all blocks example and labels
sed -n '75,140p' hypersync-net-types/src/query.rs

Length of output: 2891


🏁 Script executed:

#!/bin/bash
# Print lines 140-200 to inspect the “Include all blocks” example and labels
sed -n '140,200p' hypersync-net-types/src/query.rs

Length of output: 3787


Fix doctest wrappers and correct typo in include_all_blocks doc

  • Wrap each example using the ? operator in a fn main() -> anyhow::Result<()> { … } (hidden with #) or mark the fence as rust,no_run so Rustdoc compiles without errors (applies to all code blocks throughout the file).
  • In the Query struct doc comment, change “Weather to include all blocks…” to “Whether to include all blocks…”.
🤖 Prompt for AI Agents
In hypersync-net-types/src/query.rs around lines 32 to 55, the doctest examples
use the `?` operator unwrapped and one docstring contains a typo ("Weather"
should be "Whether"); update all offending code blocks in this file by either
wrapping examples that use `?` in a hidden `# fn main() -> anyhow::Result<()> {
... }` wrapper or change the code fence to `rust,no_run` so rustdoc can compile
them, and correct the typo in the `Query` struct doc comment from "Weather to
include all blocks…" to "Whether to include all blocks…".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
hypersync-net-types/src/query.rs (1)

32-688: Docs still break doctests and contain unresolved nits.
All of these Query examples continue to use ? without hiding a fn main() -> anyhow::Result<()> { … }, so cargo test --doc still fails. Please wrap each fence (or mark it rust,no_run) the same way across this doc. Also, the include_all_blocks field docs still say “Weather to include…” and the “Normal mode – only blocks with matching logs” example still calls .include_all_blocks(), contradicting the label. Those were raised earlier and need to be corrected.

-/// ```
-/// use hypersync_net_types::{ Query, FieldSelection, LogFilter, BlockFilter, TransactionFilter };
+/// ```
+/// # fn main() -> anyhow::Result<()> {
+/// use hypersync_net_types::{ Query, FieldSelection, LogFilter, BlockFilter, TransactionFilter };
 /// let usdt_transfers = Query::new()
 ///     .from_block(18_000_000)
 ///     .where_logs([/* … */])?;
-/// # Ok::<(), anyhow::Error>(())
+/// # Ok::<(), anyhow::Error>(())
+/// # }
 /// ```
@@
-    /// Weather to include all blocks regardless of if they are related…
+    /// Whether to include all blocks regardless of whether they are related…
@@
-    /// // Normal mode - only blocks with matching logs
+    /// // Normal mode - only blocks with matching logs
 /// let query = Query::new()
 ///     .from_block(18_000_000)
-///     .include_all_blocks()
 ///     .where_logs([/* … */]);
examples/call_watch/src/main.rs (1)

26-28: Restore DAI transfer filter to OR semantics.
Chaining .and_from_address([DAI_ADDRESS])? with .and_to_address([DAI_ADDRESS])? forces both sender and recipient to be the DAI contract, so every real transfer (user → DAI or DAI → user) is filtered out and the example streams nothing. Drop the from restriction (or move it into a separate filter) so transfers are actually captured.

             hypersync_client::net_types::TransactionFilter::any()
-                .and_from_address([DAI_ADDRESS])?
                 .and_to_address([DAI_ADDRESS])?,
examples/reverse_wallet/src/main.rs (1)

29-31: Split sender/recipient filters so transfers aren’t dropped.
Combining .and_from_address([address])? and .and_to_address([address])? on the same TransactionFilter demands both ends equal address, killing every real transfer. Provide two filters so match_transactions_any keeps its OR behavior.

-        .where_transactions([TransactionFilter::any()
-            .and_from_address([address])?
-            .and_to_address([address])?]);
+        .where_transactions([
+            TransactionFilter::any().and_from_address([address])?,
+            TransactionFilter::any().and_to_address([address])?,
+        ]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1168564 and 0d0f099.

📒 Files selected for processing (12)
  • examples/all_erc20/src/main.rs (4 hunks)
  • examples/call_decode_output/src/main.rs (3 hunks)
  • examples/call_watch/src/main.rs (1 hunks)
  • examples/reverse_wallet/src/main.rs (2 hunks)
  • examples/wallet/src/main.rs (4 hunks)
  • examples/watch/src/main.rs (2 hunks)
  • hypersync-net-types/src/block.rs (4 hunks)
  • hypersync-net-types/src/lib.rs (3 hunks)
  • hypersync-net-types/src/log.rs (5 hunks)
  • hypersync-net-types/src/query.rs (6 hunks)
  • hypersync-net-types/src/trace.rs (5 hunks)
  • hypersync-net-types/src/transaction.rs (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
hypersync-net-types/src/block.rs (2)
hypersync-net-types/src/transaction.rs (3)
  • any (21-23)
  • any (168-170)
  • and_hash (475-490)
hypersync-net-types/src/query.rs (3)
  • new (219-221)
  • new (828-830)
  • test_query_serde (1197-1248)
examples/all_erc20/src/main.rs (2)
hypersync-net-types/src/log.rs (2)
  • LogField (491-494)
  • any (25-27)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
examples/watch/src/main.rs (3)
hypersync-net-types/src/log.rs (2)
  • LogField (491-494)
  • any (25-27)
examples/all_erc20/src/main.rs (1)
  • main (17-111)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
examples/wallet/src/main.rs (6)
hypersync-net-types/src/log.rs (2)
  • LogField (491-494)
  • any (25-27)
hypersync-net-types/src/transaction.rs (3)
  • TransactionField (1188-1191)
  • any (21-23)
  • any (168-170)
examples/all_erc20/src/main.rs (1)
  • main (17-111)
examples/call_watch/src/main.rs (1)
  • main (13-80)
examples/watch/src/main.rs (1)
  • main (13-78)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
hypersync-net-types/src/log.rs (4)
hypersync-net-types/src/block.rs (1)
  • any (26-28)
hypersync-net-types/src/trace.rs (2)
  • any (52-54)
  • and_address (195-211)
hypersync-net-types/src/transaction.rs (3)
  • any (21-23)
  • any (168-170)
  • and_address (98-114)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
hypersync-net-types/src/lib.rs (5)
hypersync-net-types/src/query.rs (6)
  • block (862-865)
  • log (939-942)
  • trace (990-993)
  • transaction (900-903)
  • new (219-221)
  • new (828-830)
hypersync-net-types/src/block.rs (1)
  • BlockField (391-394)
hypersync-net-types/src/log.rs (1)
  • LogField (491-494)
hypersync-net-types/src/trace.rs (1)
  • TraceField (756-759)
hypersync-net-types/src/transaction.rs (1)
  • TransactionField (1188-1191)
hypersync-net-types/src/transaction.rs (4)
hypersync-net-types/src/block.rs (2)
  • any (26-28)
  • and_hash (67-82)
hypersync-net-types/src/log.rs (2)
  • any (25-27)
  • and_address (66-82)
hypersync-net-types/src/trace.rs (5)
  • any (52-54)
  • and_address (195-211)
  • and_from_address (94-110)
  • and_to_address (147-163)
  • and_sighash (345-361)
hypersync-net-types/src/query.rs (3)
  • new (219-221)
  • new (828-830)
  • test_query_serde (1197-1248)
examples/reverse_wallet/src/main.rs (2)
hypersync-net-types/src/transaction.rs (2)
  • any (21-23)
  • any (168-170)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
examples/call_decode_output/src/main.rs (2)
hypersync-net-types/src/trace.rs (2)
  • TraceField (756-759)
  • any (52-54)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
examples/call_watch/src/main.rs (4)
hypersync-net-types/src/transaction.rs (3)
  • TransactionField (1188-1191)
  • any (21-23)
  • any (168-170)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
hypersync-client/src/lib.rs (1)
  • new (71-94)
hypersync-client/src/decode_call.rs (1)
  • from_signatures (27-40)
hypersync-net-types/src/trace.rs (4)
hypersync-net-types/src/block.rs (2)
  • any (26-28)
  • all (272-275)
hypersync-net-types/src/log.rs (3)
  • any (25-27)
  • and_address (66-82)
  • all (426-429)
hypersync-net-types/src/transaction.rs (8)
  • any (21-23)
  • any (168-170)
  • and_from_address (209-225)
  • and_to_address (261-277)
  • and_address (98-114)
  • and_type (383-389)
  • and_sighash (314-330)
  • all (939-942)
hypersync-net-types/src/query.rs (2)
  • new (219-221)
  • new (828-830)
hypersync-net-types/src/query.rs (2)
hypersync-net-types/src/lib.rs (3)
  • new (64-69)
  • from_reader (111-113)
  • from_reader (144-154)
hypersync-net-types/src/request.rs (1)
  • from_reader (71-103)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test_release
🔇 Additional comments (4)
examples/all_erc20/src/main.rs (1)

29-43: Builder migration matches prior JSON semantics.

from_block, the ERC20 transfer topic0 filter, and the explicit topic/data field selection line up with the old literal and keep the intent readable. Looks great.

examples/watch/src/main.rs (1)

24-38: Clearer log filter composition looks good.

The fluent LogFilter chain mirrors the prior JSON while making the DAI address and transfer topic constraints explicit—no issues spotted.

hypersync-net-types/src/block.rs (1)

67-142: Well-scoped builder helpers.

and_hash / and_miner_address provide the fluent ergonomics we need and wrap conversion failures with index-aware context, which will make misconfigured filters much easier to debug.

hypersync-net-types/src/log.rs (1)

84-278: Topic builder handles sparse indices cleanly.

The guard on topic index ≤3 plus the pre-fill of missing slots keeps serialization consistent while letting callers jump straight to topic3—nicely done.

Comment on lines +33 to +361
impl TraceFilter {
/// Create a trace filter that matches any trace.
///
/// This creates an empty filter with no constraints, which will match all traces.
/// You can then use the builder methods to add specific filtering criteria.
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Create a filter that matches any trace
/// let filter = TraceFilter::any();
///
/// // Chain with other filter methods
/// let filter = TraceFilter::any()
/// .and_from_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn any() -> Self {
Default::default()
}

/// Filter traces by any of the provided "from" addresses.
///
/// This method accepts any iterable of values that can be converted to `Address`.
/// Common input types include string slices, byte arrays, and `Address` objects.
/// The "from" address typically represents the caller or originator of the trace.
///
/// # Arguments
/// * `addresses` - An iterable of addresses to filter by
///
/// # Returns
/// * `Ok(Self)` - The updated filter on success
/// * `Err(anyhow::Error)` - If any address fails to convert
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Filter by a single caller address
/// let filter = TraceFilter::any()
/// .and_from_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
///
/// // Filter by multiple caller addresses
/// let filter = TraceFilter::any()
/// .and_from_address([
/// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567",
/// "0xdac17f958d2ee523a2206206994597c13d831ec7",
/// ])?;
///
/// // Using byte arrays
/// let caller_address = [
/// 0xa0, 0xb8, 0x6a, 0x33, 0xe6, 0xc1, 0x1c, 0x8c, 0x0c, 0x5c,
/// 0x0b, 0x5e, 0x6a, 0xde, 0xe3, 0x0d, 0x1a, 0x23, 0x45, 0x67
/// ];
/// let filter = TraceFilter::any()
/// .and_from_address([caller_address])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_from_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
where
I: IntoIterator<Item = A>,
A: TryInto<Address>,
A::Error: std::error::Error + Send + Sync + 'static,
{
let mut converted_addresses: Vec<Address> = Vec::new();
for (idx, address) in addresses.into_iter().enumerate() {
converted_addresses.push(
address
.try_into()
.with_context(|| format!("invalid from address at position {idx}"))?,
);
}
self.from = converted_addresses;
Ok(self)
}

/// Filter traces by any of the provided "to" addresses.
///
/// This method accepts any iterable of values that can be converted to `Address`.
/// Common input types include string slices, byte arrays, and `Address` objects.
/// The "to" address typically represents the target or recipient of the trace.
///
/// # Arguments
/// * `addresses` - An iterable of addresses to filter by
///
/// # Returns
/// * `Ok(Self)` - The updated filter on success
/// * `Err(anyhow::Error)` - If any address fails to convert
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Filter by a single target address
/// let filter = TraceFilter::any()
/// .and_to_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
///
/// // Filter by multiple target addresses
/// let filter = TraceFilter::any()
/// .and_to_address([
/// "0xdac17f958d2ee523a2206206994597c13d831ec7",
/// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567",
/// ])?;
///
/// // Chain with from address filtering
/// let filter = TraceFilter::any()
/// .and_from_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
/// .and_to_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_to_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
where
I: IntoIterator<Item = A>,
A: TryInto<Address>,
A::Error: std::error::Error + Send + Sync + 'static,
{
let mut converted_addresses: Vec<Address> = Vec::new();
for (idx, address) in addresses.into_iter().enumerate() {
converted_addresses.push(
address
.try_into()
.with_context(|| format!("invalid to address at position {idx}"))?,
);
}
self.to = converted_addresses;
Ok(self)
}

/// Filter traces by any of the provided contract addresses.
///
/// This method accepts any iterable of values that can be converted to `Address`.
/// Common input types include string slices, byte arrays, and `Address` objects.
/// The address field typically represents the contract address involved in the trace.
///
/// # Arguments
/// * `addresses` - An iterable of addresses to filter by
///
/// # Returns
/// * `Ok(Self)` - The updated filter on success
/// * `Err(anyhow::Error)` - If any address fails to convert
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Filter by a single contract address
/// let filter = TraceFilter::any()
/// .and_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
///
/// // Filter by multiple contract addresses
/// let filter = TraceFilter::any()
/// .and_address([
/// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567",
/// "0xdac17f958d2ee523a2206206994597c13d831ec7",
/// ])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
where
I: IntoIterator<Item = A>,
A: TryInto<Address>,
A::Error: std::error::Error + Send + Sync + 'static,
{
let mut converted_addresses: Vec<Address> = Vec::new();
for (idx, address) in addresses.into_iter().enumerate() {
converted_addresses.push(
address
.try_into()
.with_context(|| format!("invalid address at position {idx}"))?,
);
}
self.address = converted_addresses;
Ok(self)
}

/// Filter traces by any of the provided call types.
///
/// This method accepts any iterable of values that can be converted to `String`.
/// Common call types include "call", "staticcall", "delegatecall", "create", "create2", etc.
///
/// # Arguments
/// * `call_types` - An iterable of call type strings to filter by
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Filter by specific call types
/// let filter = TraceFilter::any()
/// .and_call_type(["call", "delegatecall"]);
///
/// // Filter by contract creation traces
/// let filter = TraceFilter::any()
/// .and_call_type(["create", "create2"]);
///
/// // Chain with address filtering
/// let filter = TraceFilter::any()
/// .and_from_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
/// .and_call_type(["call"]);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_call_type<I, S>(mut self, call_types: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.call_type = call_types.into_iter().map(Into::into).collect();
self
}

/// Filter traces by any of the provided reward types.
///
/// This method accepts any iterable of values that can be converted to `String`.
/// Common reward types include "block", "uncle", etc., typically used for mining rewards.
///
/// # Arguments
/// * `reward_types` - An iterable of reward type strings to filter by
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Filter by block rewards
/// let filter = TraceFilter::any()
/// .and_reward_type(["block"]);
///
/// // Filter by both block and uncle rewards
/// let filter = TraceFilter::any()
/// .and_reward_type(["block", "uncle"]);
/// ```
pub fn and_reward_type<I, S>(mut self, reward_types: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.reward_type = reward_types.into_iter().map(Into::into).collect();
self
}

/// Filter traces by any of the provided trace types.
///
/// This method accepts any iterable of values that can be converted to `String`.
/// Common trace types include "call", "create", "suicide", "reward", etc.
///
/// # Arguments
/// * `types` - An iterable of trace type strings to filter by
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Filter by call traces
/// let filter = TraceFilter::any()
/// .and_type(["call"]);
///
/// // Filter by multiple trace types
/// let filter = TraceFilter::any()
/// .and_type(["call", "create", "reward"]);
/// ```
pub fn and_type<I, S>(mut self, types: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.type_ = types.into_iter().map(Into::into).collect();
self
}

/// Filter traces by any of the provided function signature hashes (sighashes).
///
/// This method accepts any iterable of values that can be converted to `Sighash`.
/// Common input types include string slices, byte arrays, and `Sighash` objects.
/// Sighashes are the first 4 bytes of the keccak256 hash of a function signature.
///
/// # Arguments
/// * `sighashes` - An iterable of sighash values to filter by
///
/// # Returns
/// * `Ok(Self)` - The updated filter on success
/// * `Err(anyhow::Error)` - If any sighash fails to convert
///
/// # Examples
///
/// ```
/// use hypersync_net_types::TraceFilter;
///
/// // Filter by transfer function signature
/// let transfer_sig = "0xa9059cbb"; // transfer(address,uint256)
/// let filter = TraceFilter::any()
/// .and_sighash([transfer_sig])?;
///
/// // Filter by multiple function signatures
/// let filter = TraceFilter::any()
/// .and_sighash([
/// "0xa9059cbb", // transfer(address,uint256)
/// "0x095ea7b3", // approve(address,uint256)
/// ])?;
///
/// // Using byte arrays
/// let transfer_bytes = [0xa9, 0x05, 0x9c, 0xbb];
/// let filter = TraceFilter::any()
/// .and_sighash([transfer_bytes])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_sighash<I, S>(mut self, sighashes: I) -> anyhow::Result<Self>
where
I: IntoIterator<Item = S>,
S: TryInto<Sighash>,
S::Error: std::error::Error + Send + Sync + 'static,
{
let mut converted_sighashes: Vec<Sighash> = Vec::new();
for (idx, sighash) in sighashes.into_iter().enumerate() {
converted_sighashes.push(
sighash
.try_into()
.with_context(|| format!("invalid sighash at position {idx}"))?,
);
}
self.sighash = converted_sighashes;
Ok(self)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wrap fallible doctest snippets in Result-returning mains.
These examples still invoke ? inside the default doctest context, so cargo test --doc fails. Add hidden # fn main() -> anyhow::Result<()> { / # Ok(()) / # } lines (or mark the fences rust,no_run) around every snippet that uses ?, mirroring the fix needed in transaction.rs.

-/// ```
-/// use hypersync_net_types::TraceFilter;
+/// ```
+/// # fn main() -> anyhow::Result<()> {
+/// use hypersync_net_types::TraceFilter;
 /// let filter = TraceFilter::any()
 ///     .and_from_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
-/// # Ok::<(), anyhow::Error>(())
+/// # Ok::<(), anyhow::Error>(())
+/// # }
 /// ```
🤖 Prompt for AI Agents
In hypersync-net-types/src/trace.rs around lines 33 to 361, update every doctest
snippet that uses the ? operator so it becomes a Result-returning hidden main
(or mark the fence rust,no_run); specifically, wrap the snippet body with hidden
lines like # fn main() -> anyhow::Result<()> { at the start and # Ok(()) # } at
the end (keeping existing hidden Ok::<(), anyhow::Error>(()) lines if present)
so the examples compile under cargo test --doc without changing behavior.

Comment on lines +25 to +114
/// Filter authorizations by any of the provided chain IDs.
///
/// # Arguments
/// * `chain_ids` - An iterable of chain IDs to filter by
///
/// # Examples
///
/// ```
/// use hypersync_net_types::AuthorizationSelection;
///
/// // Filter by a single chain ID (Ethereum mainnet)
/// let selection = AuthorizationSelection::any()
/// .and_chain_id([1]);
///
/// // Filter by multiple chain IDs
/// let selection = AuthorizationSelection::any()
/// .and_chain_id([
/// 1, // Ethereum mainnet
/// 137, // Polygon
/// 42161, // Arbitrum One
/// ]);
///
/// // Chain with address filter
/// let selection = AuthorizationSelection::any()
/// .and_chain_id([1, 137])
/// .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_chain_id<I>(mut self, chain_ids: I) -> Self
where
I: IntoIterator<Item = u64>,
{
self.chain_id = chain_ids.into_iter().collect();
self
}

/// Filter authorizations by any of the provided addresses.
///
/// This method accepts any iterable of values that can be converted to `Address`.
/// Common input types include string slices, byte arrays, and `Address` objects.
///
/// # Arguments
/// * `addresses` - An iterable of addresses to filter by
///
/// # Returns
/// * `Ok(Self)` - The updated selection on success
/// * `Err(anyhow::Error)` - If any address fails to convert
///
/// # Examples
///
/// ```
/// use hypersync_net_types::AuthorizationSelection;
///
/// // Filter by a single address
/// let selection = AuthorizationSelection::any()
/// .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
///
/// // Filter by multiple addresses
/// let selection = AuthorizationSelection::any()
/// .and_address([
/// "0xdac17f958d2ee523a2206206994597c13d831ec7", // Address 1
/// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // Address 2
/// ])?;
///
/// // Using byte arrays
/// let auth_address = [
/// 0xda, 0xc1, 0x7f, 0x95, 0x8d, 0x2e, 0xe5, 0x23, 0xa2, 0x20,
/// 0x62, 0x06, 0x99, 0x45, 0x97, 0xc1, 0x3d, 0x83, 0x1e, 0xc7
/// ];
/// let selection = AuthorizationSelection::any()
/// .and_address([auth_address])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
where
I: IntoIterator<Item = A>,
A: TryInto<Address>,
A::Error: std::error::Error + Send + Sync + 'static,
{
let mut converted_addresses: Vec<Address> = Vec::new();
for (idx, address) in addresses.into_iter().enumerate() {
converted_addresses.push(
address
.try_into()
.with_context(|| format!("invalid authorization address at position {idx}"))?,
);
}
self.address = converted_addresses;
Ok(self)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix doctest wrappers for fallible examples.
Every code fence here still uses ? inside the default doctest fn main() { … }, so cargo test --doc fails. Wrap the snippets in a hidden fn main() -> anyhow::Result<()> { … } (closing with # Ok(())\n# }) or mark the fences rust,no_run. Apply the same pattern to all fallible examples in this file.

-/// ```
-/// use hypersync_net_types::AuthorizationSelection;
+/// ```
+/// # fn main() -> anyhow::Result<()> {
+/// use hypersync_net_types::AuthorizationSelection;
 /// let selection = AuthorizationSelection::any()
 ///     .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
-/// # Ok::<(), anyhow::Error>(())
+/// # Ok::<(), anyhow::Error>(())
+/// # }
 /// ```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Filter authorizations by any of the provided chain IDs.
///
/// # Arguments
/// * `chain_ids` - An iterable of chain IDs to filter by
///
/// # Examples
///
/// ```
/// use hypersync_net_types::AuthorizationSelection;
///
/// // Filter by a single chain ID (Ethereum mainnet)
/// let selection = AuthorizationSelection::any()
/// .and_chain_id([1]);
///
/// // Filter by multiple chain IDs
/// let selection = AuthorizationSelection::any()
/// .and_chain_id([
/// 1, // Ethereum mainnet
/// 137, // Polygon
/// 42161, // Arbitrum One
/// ]);
///
/// // Chain with address filter
/// let selection = AuthorizationSelection::any()
/// .and_chain_id([1, 137])
/// .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_chain_id<I>(mut self, chain_ids: I) -> Self
where
I: IntoIterator<Item = u64>,
{
self.chain_id = chain_ids.into_iter().collect();
self
}
/// Filter authorizations by any of the provided addresses.
///
/// This method accepts any iterable of values that can be converted to `Address`.
/// Common input types include string slices, byte arrays, and `Address` objects.
///
/// # Arguments
/// * `addresses` - An iterable of addresses to filter by
///
/// # Returns
/// * `Ok(Self)` - The updated selection on success
/// * `Err(anyhow::Error)` - If any address fails to convert
///
/// # Examples
///
/// ```
/// use hypersync_net_types::AuthorizationSelection;
///
/// // Filter by a single address
/// let selection = AuthorizationSelection::any()
/// .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
///
/// // Filter by multiple addresses
/// let selection = AuthorizationSelection::any()
/// .and_address([
/// "0xdac17f958d2ee523a2206206994597c13d831ec7", // Address 1
/// "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // Address 2
/// ])?;
///
/// // Using byte arrays
/// let auth_address = [
/// 0xda, 0xc1, 0x7f, 0x95, 0x8d, 0x2e, 0xe5, 0x23, 0xa2, 0x20,
/// 0x62, 0x06, 0x99, 0x45, 0x97, 0xc1, 0x3d, 0x83, 0x1e, 0xc7
/// ];
/// let selection = AuthorizationSelection::any()
/// .and_address([auth_address])?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn and_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
where
I: IntoIterator<Item = A>,
A: TryInto<Address>,
A::Error: std::error::Error + Send + Sync + 'static,
{
let mut converted_addresses: Vec<Address> = Vec::new();
for (idx, address) in addresses.into_iter().enumerate() {
converted_addresses.push(
address
.try_into()
.with_context(|| format!("invalid authorization address at position {idx}"))?,
);
}
self.address = converted_addresses;
Ok(self)
}
/// Filter authorizations by any of the provided chain IDs.
///
/// # Arguments
/// * `chain_ids` - An iterable of chain IDs to filter by
///
/// # Examples
///
///

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants