Skip to content

refactor: pagination system #978

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

refactor: pagination system #978

wants to merge 4 commits into from

Conversation

gfyrag
Copy link
Contributor

@gfyrag gfyrag commented Jun 19, 2025

  • chore: move code
  • refacto: pagination system
  • feat: add sane default for each resource handler

@gfyrag gfyrag requested a review from a team as a code owner June 19, 2025 17:41
Copy link

coderabbitai bot commented Jun 19, 2025

"""

Walkthrough

This change unifies and generalizes the pagination query types used throughout the codebase. It replaces specific types like OffsetPaginatedQuery and ColumnPaginatedQuery with a single, generic PaginatedQuery type in controller interfaces, implementations, mocks, and tests. Pagination query construction is refactored and simplified, and new generic utilities for paginated queries and cursors are introduced.

Changes

Files/Group Change Summary
internal/storage/common/pagination.go, cursor.go, schema.go New generic types for paginated queries and schema field types; utilities for extracting, encoding, and iterating paginated queries and cursors.
internal/storage/common/resource.go Refactored paginated resource repository to use generic PaginatedQuery; removed field/operator logic; added schema field lookup by alias.
internal/storage/common/paginator.go, paginator_column.go, paginator_offset.go Simplified paginator interfaces and implementations to use unified query types; encapsulated pagination parameters; refactored column and offset paginator logic.
internal/storage/ledger/store.go, system/store.go, ledger/accounts.go Updated resource repository creation to use new generic paginated resource constructors; removed explicit paginator construction; minor newline removal.
internal/controller/ledger/controller.go, controller_default.go, controller_with_traces.go Controller interfaces and implementations now use PaginatedQuery for all paginated methods; updated signatures and internal usage.
internal/controller/ledger/store.go, store_generated_test.go, stats_test.go, mocks_test.go Store interfaces and mocks updated to remove pagination query type parameter; mocks and tests refactored to use new paginated query types and constructors.
internal/controller/system/controller.go, store.go System controller and store interfaces now use PaginatedQuery; updated method signatures and implementations.
internal/api/v1/controllers_.go, v2/controllers_.go Handlers updated to use new getPaginatedQuery utility; replaced old pagination query extraction; updated calls to controller methods.
internal/api/v1/controllers_test.go, v2/controllers_test.go Tests updated to use InitialPaginatedQuery or PaginatedQuery in place of specific pagination query types; cursor encoding/decoding adjusted accordingly; test function renames.
internal/api/bulking/mocks_ledger_controller_test.go, v1/mocks_ledger_controller_test.go, ... Mock controller methods updated to use PaginatedQuery instead of specific pagination query types.
internal/api/common/mocks_ledger_controller_test.go, mocks_system_controller_test.go Mock method signatures updated for unified pagination query types.
internal/storage/ledger/accounts_test.go, logs_test.go, transactions_test.go, volumes_test.go Tests updated to use InitialPaginatedQuery in pagination calls.
internal/storage/system/resource_ledgers.go Added numeric "id" field to ledgers resource schema.
internal/storage/driver/driver.go, driver_test.go, system_generated_test.go Driver and system store interfaces updated to use unified paginated query types; test updates reflect new query construction.
internal/api/v1/utils.go, v2/common.go Consolidated pagination query extraction into a single generic getPaginatedQuery function; removed old offset/column-specific functions.
internal/api/v1/controllers_config.go, internal/worker/async_block.go, storage/bucket/migrations_test.go Refactored iteration over paginated results to use new generic utilities; updated paginated query construction and iteration logic.
test/e2e/api_logs_list_test.go Minor fix to test description string.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HTTP Handler
    participant PaginationUtil
    participant Controller
    participant Store

    Client->>HTTP Handler: HTTP request with pagination params/cursor
    HTTP Handler->>PaginationUtil: getPaginatedQuery(request, ...)
    PaginationUtil-->>HTTP Handler: PaginatedQuery
    HTTP Handler->>Controller: ListX(ctx, PaginatedQuery)
    Controller->>Store: Paginate(ctx, PaginatedQuery)
    Store-->>Controller: Cursor[Resource]
    Controller-->>HTTP Handler: Cursor[Resource]
    HTTP Handler-->>Client: Paginated response with cursor
Loading

Possibly related PRs

  • formancehq/ledger#615: Refactors LedgerController mock methods to use generic pagination query types, directly related to this change.

Suggested labels

build-images

Suggested reviewers

  • paul-nicolas
  • flemzord

Poem

Hopping through the fields of code,
Where queries once diverged and flowed,
Now unified, they leap as one,
Pagination’s work is neatly done.
With every hop, a page appears,
The rabbit’s joy rings in your ears!
🐇✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment
  • Commit Unit Tests in branch refacto/pagination-system

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

cursor[bot]

This comment was marked as outdated.

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: 5

🧹 Nitpick comments (3)
internal/api/v2/controllers_volumes.go (1)

53-77: Pagination query construction is more explicit but adds complexity.

The new getPaginatedQuery approach with modifier functions provides better configurability and explicit default ordering. However, this increases the code complexity compared to the previous approach. Consider whether this added complexity is justified by the improved flexibility.

The explicit configuration is more maintainable, but you might consider extracting the modifier function to improve readability:

+func configureVolumesQuery(groupBy int, pit, oot *time.Time, useInsertionDate bool) func(*storagecommon.ResourceQuery[ledgercontroller.GetVolumesOptions]) {
+	return func(rq *storagecommon.ResourceQuery[ledgercontroller.GetVolumesOptions]) {
+		if groupBy > 0 {
+			rq.Opts.GroupLvl = groupBy
+		}
+		if pit != nil {
+			rq.PIT = pit
+		}
+		if oot != nil {
+			rq.OOT = oot
+		}
+		rq.Opts.UseInsertionDate = useInsertionDate
+	}
+}

 rq, err := getPaginatedQuery[ledgercontroller.GetVolumesOptions](
 	r,
 	paginationConfig,
 	"account",
 	bunpaginate.OrderAsc,
-	func(rq *storagecommon.ResourceQuery[ledgercontroller.GetVolumesOptions]) {
-		if groupBy > 0 {
-			rq.Opts.GroupLvl = groupBy
-		}
-		if pit != nil {
-			rq.PIT = pit
-		}
-		if oot != nil {
-			rq.OOT = oot
-		}
-		rq.Opts.UseInsertionDate = api.QueryParamBool(r, "insertionDate")
-	},
+	configureVolumesQuery(groupBy, pit, oot, api.QueryParamBool(r, "insertionDate")),
 )
internal/storage/common/cursor.go (1)

30-69: Consider improving type safety in cursor unmarshaling

The current approach uses reflection and type assertions which could be made more type-safe. Additionally, the comment on line 36 suggests this is a temporary solution.

Consider implementing a more robust cursor type detection mechanism that doesn't rely on checking for the presence of specific fields. This could involve:

  • Adding a cursor type field to the serialized cursor
  • Using a discriminated union pattern
  • Implementing separate cursor types with their own unmarshal methods
internal/storage/common/resource.go (1)

310-314: Remove redundant validation.

The pagination column validation is performed twice - once during the normalization of InitialPaginatedQuery (lines 283-286) and again here. Since the field was already validated and retrieved above, this second validation is unnecessary.

 	case ColumnPaginatedQuery[OptionsType]:
 		fieldName, field := r.resourceHandler.Schema().GetFieldByNameOrAlias(v.Column)
-		if field == nil {
-			return nil, fmt.Errorf("invalid property '%s' for pagination", v.Column)
-		}
 		paginator = newColumnPaginator[ResourceType, OptionsType](v, fieldName, field.Type)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 539800c and d537e72.

⛔ Files ignored due to path filters (1)
  • go.mod is excluded by !**/*.mod
📒 Files selected for processing (60)
  • internal/api/bulking/mocks_ledger_controller_test.go (4 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (4 hunks)
  • internal/api/common/mocks_system_controller_test.go (1 hunks)
  • internal/api/v1/controllers_accounts_list.go (2 hunks)
  • internal/api/v1/controllers_accounts_list_test.go (8 hunks)
  • internal/api/v1/controllers_balances_list.go (2 hunks)
  • internal/api/v1/controllers_config.go (1 hunks)
  • internal/api/v1/controllers_logs_list.go (2 hunks)
  • internal/api/v1/controllers_logs_list_test.go (5 hunks)
  • internal/api/v1/controllers_transactions_list.go (2 hunks)
  • internal/api/v1/controllers_transactions_list_test.go (11 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (4 hunks)
  • internal/api/v1/mocks_system_controller_test.go (1 hunks)
  • internal/api/v1/utils.go (1 hunks)
  • internal/api/v2/common.go (1 hunks)
  • internal/api/v2/controllers_accounts_list.go (1 hunks)
  • internal/api/v2/controllers_accounts_list_test.go (7 hunks)
  • internal/api/v2/controllers_ledgers_list.go (2 hunks)
  • internal/api/v2/controllers_ledgers_list_test.go (4 hunks)
  • internal/api/v2/controllers_logs_list.go (1 hunks)
  • internal/api/v2/controllers_logs_list_test.go (7 hunks)
  • internal/api/v2/controllers_transactions_list.go (1 hunks)
  • internal/api/v2/controllers_transactions_list_test.go (13 hunks)
  • internal/api/v2/controllers_volumes.go (2 hunks)
  • internal/api/v2/controllers_volumes_test.go (5 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (4 hunks)
  • internal/api/v2/mocks_system_controller_test.go (1 hunks)
  • internal/controller/ledger/controller.go (1 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_default_test.go (8 hunks)
  • internal/controller/ledger/controller_generated_test.go (4 hunks)
  • internal/controller/ledger/controller_with_traces.go (4 hunks)
  • internal/controller/ledger/mocks_test.go (4 hunks)
  • internal/controller/ledger/stats_test.go (1 hunks)
  • internal/controller/ledger/store.go (1 hunks)
  • internal/controller/ledger/store_generated_test.go (4 hunks)
  • internal/controller/system/controller.go (2 hunks)
  • internal/controller/system/store.go (1 hunks)
  • internal/storage/bucket/migrations_test.go (1 hunks)
  • internal/storage/common/cursor.go (1 hunks)
  • internal/storage/common/pagination.go (1 hunks)
  • internal/storage/common/paginator.go (1 hunks)
  • internal/storage/common/paginator_column.go (6 hunks)
  • internal/storage/common/paginator_offset.go (2 hunks)
  • internal/storage/common/resource.go (6 hunks)
  • internal/storage/common/schema.go (1 hunks)
  • internal/storage/driver/driver.go (1 hunks)
  • internal/storage/driver/driver_test.go (2 hunks)
  • internal/storage/driver/system_generated_test.go (1 hunks)
  • internal/storage/ledger/accounts.go (1 hunks)
  • internal/storage/ledger/accounts_test.go (15 hunks)
  • internal/storage/ledger/logs_test.go (2 hunks)
  • internal/storage/ledger/store.go (2 hunks)
  • internal/storage/ledger/transactions_test.go (13 hunks)
  • internal/storage/ledger/volumes_test.go (29 hunks)
  • internal/storage/system/resource_ledgers.go (1 hunks)
  • internal/storage/system/store.go (2 hunks)
  • internal/storage/system/store_test.go (2 hunks)
  • internal/worker/async_block.go (2 hunks)
  • test/e2e/api_logs_list_test.go (1 hunks)
🔇 Additional comments (92)
internal/storage/ledger/accounts.go (1)

140-140: LGTM - Minor formatting cleanup.

Removing the trailing newline is a good housekeeping change.

test/e2e/api_logs_list_test.go (1)

260-260: Good fix - Corrected misleading test description.

The test description now accurately reflects that it's testing log listing, not account listing.

internal/api/v2/controllers_logs_list.go (2)

17-17: LGTM - Unified pagination query construction.

The change from getColumnPaginatedQuery to getPaginatedQuery[any] aligns with the pagination system refactor to use a generic PaginatedQuery type.


23-23: LGTM - Consistent with new pagination interface.

Passing rq directly instead of dereferencing *rq is consistent with the updated pagination interface that works with values rather than pointers.

internal/api/v2/controllers_accounts_list.go (2)

18-18: LGTM - Consistent pagination unification.

The change from getOffsetPaginatedQuery to getPaginatedQuery[any] with explicit ordering parameters ("address", bunpaginate.OrderAsc) maintains the same functionality while using the unified pagination interface.


24-24: LGTM - Updated method signature.

Passing query directly aligns with the updated ListAccounts method signature that now accepts PaginatedQuery by value.

internal/storage/system/resource_ledgers.go (1)

26-26: LGTM - Added ID field for pagination support.

Adding the numeric "id" field to the schema provides a consistent field for ordering and cursor-based pagination, supporting the unified pagination system.

internal/api/v2/controllers_ledgers_list.go (1)

19-19: Pagination refactoring looks good.

The changes from getColumnPaginatedQuery to getPaginatedQuery and passing the query object by value instead of dereferencing align well with the unified pagination approach described in the PR.

Also applies to: 26-26

internal/storage/ledger/volumes_test.go (1)

109-113: Pagination type updates are consistent with the refactoring.

All instances of common.OffsetPaginatedQuery have been correctly replaced with common.InitialPaginatedQuery, maintaining the same test functionality while using the new unified pagination approach.

Also applies to: 138-143, 168-174, 181-181, 188-195, 212-219, 226-233, 240-247, 264-268, 285-289, 296-300, 307-311, 328-336, 353-361, 378-383, 400-405, 424-431, 450-455, 464-469, 478-483, 549-556, 563-571, 578-586, 593-601, 609-618, 644-653, 669-679, 714-724, 734-745, 755-763

internal/api/v2/controllers_transactions_list.go (1)

28-28: Pagination refactoring implemented correctly.

The migration from getColumnPaginatedQuery to getPaginatedQuery and the change from dereferencing the query pointer to passing by value are consistent with the unified pagination approach.

Also applies to: 34-34

internal/controller/ledger/stats_test.go (1)

22-23: Mock constructor updates align with pagination refactoring.

The removal of the third generic parameter from NewMockPaginatedResource constructors is consistent with the unification of pagination query types described in the PR.

internal/storage/system/store_test.go (2)

14-14: Import alias added for consistency.

The storagecommon import alias helps distinguish between different common packages and aligns with the refactoring.


94-96: Pagination query construction updated correctly.

The replacement of ledgercontroller.NewListLedgersQuery with storagecommon.InitialPaginatedQuery[any] and the update to use storagecommon.ColumnPaginatedQuery[any] are consistent with the unified pagination approach.

Also applies to: 102-102

internal/storage/bucket/migrations_test.go (1)

93-103: LGTM! Pagination refactoring implemented correctly.

The changes successfully migrate from the old pagination system to the unified approach:

  • Replaced bunpaginate.Iterate with common.Iterate
  • Updated query type from ColumnPaginatedQuery to InitialPaginatedQuery
  • Simplified function call by passing store.Logs().Paginate directly

The functionality is preserved while using the new unified pagination API.

internal/storage/driver/driver_test.go (2)

12-12: Import updated correctly for pagination refactoring.

The import change from ledgercontroller to storagecommon aligns with the unified pagination query types.


105-109: Query construction improved with explicit initialization.

The new approach replaces the constructor function with direct struct initialization, making the query building more explicit and transparent. The embedded ResourceQuery with the builder filter maintains the same filtering logic.

internal/api/v1/controllers_transactions_list.go (3)

4-4: Import added for unified pagination types.

The storagecommon import is necessary for the new generic pagination query types.


15-18: Pagination query construction unified and improved.

The change from getColumnPaginatedQuery to the generic getPaginatedQuery with a configuration callback is cleaner and more flexible. The callback approach allows setting both the expand field and query builder in a single configuration step.


24-24: Method call updated for new pagination API.

Passing the query by value instead of dereferencing aligns with the new method signature expectations.

internal/storage/driver/system_generated_test.go (1)

137-141: Generated mock updated correctly for simplified pagination interface.

The method signature simplification removes the redundant third generic parameter, and the type assertion is updated accordingly. As generated code, these changes correctly reflect the interface changes.

internal/storage/driver/driver.go (1)

183-185: Method signature updated for unified pagination interface.

The change from common.ColumnPaginatedQuery[any] to common.PaginatedQuery[any] aligns with the pagination system refactoring. The method body remains unchanged, maintaining functionality while using the more general pagination interface.

internal/storage/ledger/transactions_test.go (2)

455-460: LGTM: Consistent pagination query type updates.

The change from ColumnPaginatedQuery to InitialPaginatedQuery aligns with the broader pagination system refactoring. The test logic remains intact while adopting the unified pagination query interface.


743-750: LGTM: Test case structure updated consistently.

The test case struct field and initialization consistently use the new InitialPaginatedQuery type, maintaining the same test behavior while conforming to the unified pagination interface.

internal/api/v1/controllers_logs_list.go (2)

4-4: LGTM: Added import for unified pagination types.

The import of storagecommon supports the new unified pagination query approach.


40-48: LGTM: Improved pagination query construction.

The refactoring consolidates pagination query setup into a single getPaginatedQuery call with a callback, replacing the previous two-step process. This is more elegant and aligns with the unified pagination system while maintaining the same functionality.

internal/api/v1/controllers_balances_list.go (2)

4-4: LGTM: Added import for unified pagination types.

The import supports the new unified pagination query approach consistent with other controller files.


16-31: LGTM: Streamlined pagination query construction.

The refactoring improves code organization by:

  • Building the filter query first
  • Using getPaginatedQuery with a callback to configure both Expand and Builder fields in one step
  • Passing the query directly without dereferencing

This maintains the same functionality while following the unified pagination pattern.

internal/controller/system/store.go (1)

16-16: LGTM: Interface updated for unified pagination.

The change from ColumnPaginatedQuery[any] to PaginatedQuery[any] aligns the interface with the unified pagination system, providing more flexibility while maintaining compatibility.

internal/storage/system/store.go (2)

24-24: LGTM: Simplified interface signature.

Removing the generic pagination query type parameter aligns with the unified pagination approach and reduces interface complexity.


98-102: LGTM: Streamlined pagination resource creation.

The simplified call to NewPaginatedResourceRepository with explicit pagination column and order parameters is cleaner than the previous struct-based approach, while maintaining the same functionality.

internal/api/v1/mocks_system_controller_test.go (1)

104-104: LGTM! Mock signature correctly updated for pagination refactor.

The method signature change from common.ColumnPaginatedQuery[any] to common.PaginatedQuery[any] correctly reflects the pagination unification refactor. Since this is a generated mock, it properly mirrors the underlying interface change.

internal/api/v2/mocks_system_controller_test.go (1)

104-104: LGTM! Consistent pagination refactor in v2 mock.

The signature change to common.PaginatedQuery[any] is consistent with the pagination unification across the codebase and correctly reflects the underlying interface change.

internal/storage/ledger/accounts_test.go (1)

80-281: LGTM! Systematic update to unified pagination query type.

All test cases have been correctly updated to use common.InitialPaginatedQuery[any] instead of common.OffsetPaginatedQuery[any]. This change is semantically appropriate since these tests are all performing initial pagination requests without existing cursors, and aligns with the pagination unification refactor.

internal/api/v1/controllers_config.go (1)

38-41: LGTM! Improved iteration pattern with unified pagination utilities.

The refactor from direct bunpaginate.Iterate to storagecommon.Iterate with InitialPaginatedQuery improves consistency with the pagination unification. The fixed PageSize of 100 is reasonable for ledger listing, and the new pattern is more generic and maintainable.

internal/api/common/mocks_system_controller_test.go (1)

104-104: LGTM! Consistent pagination refactor across all API versions.

The mock signature change to common.PaginatedQuery[any] maintains consistency with the pagination unification refactor across v1, v2, and common API packages.

internal/worker/async_block.go (2)

10-10: LGTM: Import alias improves clarity.

The change from common to storagecommon makes the import more explicit and avoids potential naming conflicts.


79-88: Excellent refactor: Pagination query construction is more structured.

The changes improve the code in several ways:

  • The InitialPaginatedQuery with embedded ResourceQuery is more explicit about the filtering criteria
  • The feature flag filter (FeatureHashLogs = "ASYNC") is clearly defined in the query construction
  • The simplified iterator call removes unnecessary wrapper function
  • Using storagecommon.Iterate maintains consistency with the new pagination abstraction
internal/api/v2/controllers_logs_list_test.go (2)

26-26: Good: Function renamed for consistency.

The rename from TestGetLogs to TestLogsList aligns with naming conventions used across other test files.


33-153: Excellent: Test cases consistently updated for pagination refactor.

All test cases have been properly updated to use the new pagination query types:

  • Expected query type changed from specific ColumnPaginatedQuery to generic PaginatedQuery
  • All instances of ColumnPaginatedQuery[any] replaced with InitialPaginatedQuery[any]
  • The cursor encoding test case correctly embeds InitialPaginatedQuery inside ColumnPaginatedQuery
  • Query structure and validation logic remain intact

The changes maintain test coverage while supporting the unified pagination interface.

internal/storage/ledger/logs_test.go (1)

133-262: Consistent update: Pagination query types properly migrated.

All pagination calls have been correctly updated from ColumnPaginatedQuery to InitialPaginatedQuery:

  • Line 133: Concurrency test updated with ascending order specification
  • Lines 223, 230, 238, 254: Various pagination scenarios properly migrated
  • Line 246: Explicit ascending order added for clarity in date range filtering

The test logic remains intact while supporting the new unified pagination interface.

internal/api/v2/controllers_ledgers_list_test.go (3)

5-5: Good: Added necessary import for order specification.

The pointer package import is required for the pointer.For(bunpaginate.Order(bunpaginate.OrderAsc)) usage in the mock expectations.


25-25: Consistent: Function renamed to match naming convention.

The rename from TestListLedgers to TestLedgersList maintains consistency with other test function names in the codebase.


87-94: Excellent: Mock expectations explicitly specify pagination parameters.

The mock setup now explicitly constructs InitialPaginatedQuery[any] with all required fields:

  • PageSize: 15 - matches the expected page size
  • Column: "id" - specifies the pagination column
  • Order: pointer.For(bunpaginate.Order(bunpaginate.OrderAsc)) - explicit ascending order
  • Options.Expand: []string{} - empty expand slice for resource queries

This makes the test expectations more explicit and maintainable compared to the previous ledgercontroller.NewListLedgersQuery(15) approach.

internal/api/v1/controllers_accounts_list.go (2)

4-5: Good: Added necessary imports for pagination refactor.

The bunpaginate and storagecommon imports are required for the new unified pagination query construction.


17-31: Excellent refactor: Simplified pagination query construction.

The changes improve the code structure significantly:

Before: Two-step process with separate query construction and builder assignment
After: Single getPaginatedQuery call with clear parameters

Key improvements:

  • "address" column and bunpaginate.OrderAsc explicitly specified
  • Resource query builder applied through clean callback pattern
  • Query passed directly to ListAccounts method (not dereferenced)
  • Consistent with the unified pagination interface across the codebase
internal/api/v2/controllers_volumes_test.go (4)

28-28: LGTM: Function rename aligns with API conventions.

The rename from TestGetVolumes to TestVolumesList better reflects the list operation being tested.


5-5: LGTM: Proper imports and type updates for pagination refactor.

The addition of the pointer package import and the change to the unified PaginatedQuery interface align with the broader pagination refactor.

Also applies to: 35-35


44-52: LGTM: Consistent pagination defaults for volumes.

All test cases now use InitialPaginatedQuery with appropriate defaults:

  • Column: "account" makes sense for volume ordering
  • Order: ascending is appropriate for account-based sorting
  • Pointer usage is correct for the Order field

Also applies to: 57-66, 71-80, 94-105, 110-119, 124-133


108-108: LGTM: Improved test case naming.

The name change from "using exists filter" to "using exists metadata filter" provides better clarity about what's being tested.

internal/api/v1/controllers_logs_list_test.go (3)

30-30: LGTM: Type update aligns with pagination refactor.

The change from ColumnPaginatedQuery[any] to the unified PaginatedQuery[any] interface is consistent with the broader refactor.


39-43: LGTM: Appropriate pagination defaults for logs.

The use of InitialPaginatedQuery with:

  • Column: "id" for log ordering
  • Order: descending (newest logs first)
  • Proper pointer wrapping for Order field

These defaults make sense for log pagination.

Also applies to: 50-57, 64-71


76-86: LGTM: Correct cursor structure for pagination hierarchy.

The cursor encoding test correctly uses ColumnPaginatedQuery with embedded InitialPaginatedQuery. This hierarchical structure maintains compatibility with cursor-based pagination while adopting the new pagination types.

internal/api/v2/controllers_accounts_list_test.go (3)

5-5: LGTM: Proper imports and type updates for pagination refactor.

The pointer package import and the unified PaginatedQuery interface change align with the pagination refactor.

Also applies to: 33-33


44-52: LGTM: Consistent pagination defaults for accounts.

All test cases use InitialPaginatedQuery with appropriate defaults:

  • Column: "address" is logical for account ordering
  • Order: ascending provides intuitive sorting
  • Pointer usage for Order field is correct

Also applies to: 59-68, 74-83, 129-137, 143-152, 158-167, 181-189, 197-205, 213-221


89-105: LGTM: Correct cursor structure for offset-based pagination.

The cursor encoding test appropriately uses OffsetPaginatedQuery with embedded InitialPaginatedQuery, which is suitable for account pagination (offset-based rather than column-based like logs).

internal/api/v1/controllers_accounts_list_test.go (3)

4-4: LGTM: Proper imports and type updates for pagination refactor.

The pointer package import and unified PaginatedQuery interface change are consistent with the refactor.

Also applies to: 30-30


41-45: LGTM: Consistent pagination defaults for v1 accounts API.

The defaults match v2 API appropriately:

  • Column: "address" for logical account ordering
  • Order: ascending for intuitive sorting
  • Proper pointer usage for Order field

Also applies to: 53-60, 68-75, 115-119, 128-135, 143-147


80-91: Note: V1 uses column-based pagination.

The cursor encoding test uses ColumnPaginatedQuery (vs OffsetPaginatedQuery in v2). This suggests v1 and v2 APIs use different pagination strategies, which is acceptable for API versioning.

internal/api/v2/controllers_transactions_list_test.go (4)

32-32: LGTM: Type update aligns with pagination refactor.

The change to unified PaginatedQuery[any] interface is consistent with the broader refactor.


41-49: LGTM: Appropriate pagination defaults for transactions.

The use of InitialPaginatedQuery with:

  • Column: "id" for transaction ordering
  • Order: descending (newest transactions first)
  • Proper pointer wrapping for Order field

These defaults make sense for transaction listing.

Also applies to: 54-63, 68-77, 82-91, 96-105, 110-119, 124-133, 138-147, 185-193, 225-234


239-247: LGTM: Flexible column selection for different ordering strategies.

The "effective order" test case correctly uses Column: "timestamp" instead of "id", demonstrating the flexibility of the new pagination system to support different ordering strategies.


152-162: LGTM: Correct cursor structure for column-based pagination.

The cursor encoding tests appropriately use ColumnPaginatedQuery with embedded InitialPaginatedQuery, which is suitable for transaction pagination.

Also applies to: 200-220

internal/storage/common/paginator.go (1)

8-11: Interface simplification looks good.

The removal of the PaginationOptions generic parameter and corresponding method parameter simplifications are well-designed changes that:

  • Reduce interface complexity
  • Encapsulate pagination state within paginator implementations
  • Align with the unified PaginatedQuery approach across the codebase
internal/controller/system/controller.go (2)

27-27: Interface update aligns with pagination unification.

The change from common.ColumnPaginatedQuery[any] to common.PaginatedQuery[any] is consistent with the broader pagination query type standardization.


134-137: Implementation correctly matches updated interface.

The method signature and delegation to ctrl.store.ListLedgers properly reflects the interface change to use the unified PaginatedQuery type.

internal/api/v1/controllers_transactions_list_test.go (3)

30-30: Test field type correctly updated.

The expectQuery field type change from ColumnPaginatedQuery to PaginatedQuery aligns with the pagination interface unification.


39-46: Test cases properly updated to use InitialPaginatedQuery.

All test cases have been consistently updated to use storagecommon.InitialPaginatedQuery[any] instead of the previous specific pagination query types, maintaining the same test logic and expectations.

Also applies to: 53-61, 68-76, 83-91, 98-106, 113-121, 128-136, 143-151


156-172: Cursor encoding test properly handles nested structure.

The cursor encoding test correctly uses the nested structure with InitialPaginatedQuery embedded inside ColumnPaginatedQuery, which properly represents the transitional pagination query structure.

internal/controller/ledger/controller_default_test.go (2)

234-234: Mock instantiations correctly simplified.

The removal of the third generic parameter from NewMockPaginatedResource calls aligns with the simplified pagination query interface. All mock instantiations are consistently updated.

Also applies to: 265-265, 285-285, 310-310, 335-335, 355-355, 403-403, 432-432


239-243: Test method calls properly updated to use unified pagination types.

All test method calls have been consistently updated to use common.InitialPaginatedQuery[any] instead of specific pagination query types, maintaining the same test logic and expectations.

Also applies to: 247-251, 359-362, 365-368, 407-411, 414-418, 436-439, 442-445

internal/api/v2/controllers_volumes.go (3)

7-7: Import addition is appropriate.

The addition of the time package import supports the new explicit query parameter parsing approach.


20-28: Query parameter parsing is now explicit and clear.

The manual parsing of groupBy parameter with proper error handling improves code clarity compared to the previous implicit approach.


30-51: Time parameter parsing maintains compatibility.

The explicit parsing of startTime and endTime parameters with proper variable naming (pit for point-in-time, oot for out-of-time) maintains backward compatibility while improving code readability.

internal/storage/ledger/store.go (1)

47-82: LGTM! Good simplification of pagination resource creation.

The removal of the third generic parameter and the introduction of default pagination columns and orders make the code cleaner and more maintainable. The chosen defaults are sensible:

  • Accounts ordered by "address" (ascending) - logical for alphabetical listing
  • Transactions/Logs ordered by "id" (descending) - shows newest entries first
  • Volumes ordered by "account" (ascending) - consistent with account ordering
internal/controller/ledger/store.go (1)

59-63: Interface simplification looks good!

The removal of the pagination query type parameter from the Store interface methods aligns perfectly with the storage layer implementation. This simplification makes the API cleaner and easier to use while maintaining all necessary functionality through the unified PaginatedQuery approach.

internal/api/v1/utils.go (1)

26-71: Excellent consolidation of pagination query logic!

The new getPaginatedQuery function successfully unifies the previously separate offset and column pagination functions. The implementation properly:

  • Uses the generic storagecommon.Extract for cursor handling
  • Applies resource query modifiers before pagination
  • Validates page size with appropriate limits
  • Maintains backward compatibility with existing behavior
internal/api/v2/common.go (1)

64-109: Well-structured pagination consolidation with improved configurability!

The v2 implementation follows the same consolidation pattern as v1 but adds the valuable PaginationConfig parameter. This allows for endpoint-specific pagination settings, which is a great improvement for API flexibility. The implementation maintains consistency with v1 while adding this enhanced configurability.

internal/controller/ledger/store_generated_test.go (1)

49-53: Generated mock correctly updated.

The mock implementations have been properly regenerated to match the simplified Store interface signatures. All paginated resource methods now correctly use the two-parameter generic type.

Also applies to: 241-245, 300-304, 363-367

internal/api/bulking/mocks_ledger_controller_test.go (1)

244-244: LGTM: Consistent pagination query unification.

The method signatures have been correctly updated to use the unified common.PaginatedQuery interface instead of specific pagination query types. This aligns with the broader refactoring to standardize pagination handling across the codebase.

Also applies to: 288-288, 303-303, 318-318

internal/storage/common/pagination.go (1)

8-37: Well-designed pagination abstraction.

The new pagination query types provide a clean abstraction using the marker interface pattern:

  • Layered design: InitialPaginatedQuery provides the base, with OffsetPaginatedQuery and ColumnPaginatedQuery extending it for specific pagination strategies
  • Type safety: The marker interface PaginatedQuery enables type constraints while supporting different pagination implementations
  • Compile-time verification: The interface compliance checks ensure proper implementation
  • Flexibility: Generic OptionsType parameter allows for different filtering options per resource type

This unified approach should simplify pagination handling across the codebase while maintaining backwards compatibility with existing pagination strategies.

internal/api/common/mocks_ledger_controller_test.go (1)

244-244: Consistent with pagination unification.

Method signatures correctly updated to match the unified pagination interface, following the same pattern as other mock files in this refactoring.

Also applies to: 288-288, 303-303, 318-318

internal/api/v2/mocks_ledger_controller_test.go (1)

244-244: Consistent pagination interface updates.

Mock signatures properly updated to use the unified PaginatedQuery interface, maintaining consistency with the broader refactoring effort.

Also applies to: 288-288, 303-303, 318-318

internal/controller/ledger/controller_generated_test.go (1)

243-243: Pagination interface properly unified.

Controller mock signatures correctly updated to use common.PaginatedQuery, completing the consistent interface changes across all controller mocks.

Also applies to: 287-287, 302-302, 317-317

internal/controller/ledger/controller.go (1)

32-38: Interface methods correctly unified to use generic pagination type

The migration from specific pagination query types to the generic common.PaginatedQuery[any] type is consistent across all paginated methods. This unification simplifies the API surface and improves maintainability.

internal/controller/ledger/controller_with_traces.go (1)

214-223: Tracing wrapper methods correctly updated

The method signatures have been properly updated to match the interface changes, maintaining the delegation pattern to the underlying controller.

Also applies to: 262-271, 298-307, 346-355

internal/storage/common/schema.go (1)

1-254: Well-designed schema system for field types

The schema implementation provides a clean abstraction for field types with proper:

  • Operator support per type
  • Value validation
  • Indexability and pagination capabilities
  • Convenient factory functions

The type system aligns well with the unified pagination approach.

internal/api/v1/mocks_ledger_controller_test.go (1)

244-255: Mock methods correctly updated for unified pagination

The generated mock methods have been properly updated to match the interface changes, using common.PaginatedQuery instead of specific pagination query types.

Also applies to: 288-299, 303-314, 318-329

internal/storage/common/paginator_column.go (1)

252-259: ```shell
#!/bin/bash

Verify which FieldType implementations support pagination

echo "---- TypeString IsPaginated ----"
rg -n "func (t TypeString) IsPaginated" -C3 internal/storage/common/schema.go

echo -e "\n---- TypeNumeric IsPaginated ----"
rg -n "func (t TypeNumeric) IsPaginated" -C3 internal/storage/common/schema.go

echo -e "\n---- TypeBoolean IsPaginated ----"
rg -n "func (t TypeBoolean) IsPaginated" -C3 internal/storage/common/schema.go

echo -e "\n---- TypeMap IsPaginated ----"
rg -n "func (t TypeMap) IsPaginated" -C3 internal/storage/common/schema.go


</details>
<details>
<summary>internal/controller/ledger/mocks_test.go (1)</summary>

`212-316`: **LGTM!**

The mock changes correctly reflect the new unified pagination query interface.

</details>
<details>
<summary>internal/controller/ledger/controller_default.go (2)</summary>

`10-11`: **LGTM!**

The import alias change and method signature updates are consistent with the pagination system refactoring.



Also applies to: 145-183

---

`317-332`: **Clean iteration implementation.**

The migration from `bunpaginate.Iterate` to `storagecommon.Iterate` simplifies the pagination logic by removing the need to pass a pagination function.

</details>
<details>
<summary>internal/storage/common/resource.go (1)</summary>

`63-72`: **Well-implemented field lookup method.**

The `GetFieldByNameOrAlias` method provides a clean way to find fields by either their name or aliases, which supports the flexible pagination column specification.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +72 to +101
func Iterate[OF any, Options any](
ctx context.Context,
initialQuery InitialPaginatedQuery[Options],
iterator func(ctx context.Context, q PaginatedQuery[Options]) (*bunpaginate.Cursor[OF], error),
cb func(cursor *bunpaginate.Cursor[OF]) error,
) error {

var query PaginatedQuery[OF] = initialQuery
for {
cursor, err := iterator(ctx, query)
if err != nil {
return err
}

if err := cb(cursor); err != nil {
return err
}

if !cursor.HasMore {
break
}

query, err = unmarshalCursor[OF](cursor.Next)
if err != nil {
return fmt.Errorf("paginating next request: %w", err)
}
}

return nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix type parameter inconsistencies in Iterate function

There are type parameter mismatches in the Iterate function:

  1. Line 79: initialQuery is assigned to query but they have different type parameters (OF vs Options)
  2. Line 94: unmarshalCursor is called with type parameter OF but should use Options to match the iterator signature

Apply this fix:

 func Iterate[OF any, Options any](
 	ctx context.Context,
 	initialQuery InitialPaginatedQuery[Options],
 	iterator func(ctx context.Context, q PaginatedQuery[Options]) (*bunpaginate.Cursor[OF], error),
 	cb func(cursor *bunpaginate.Cursor[OF]) error,
 ) error {
 
-	var query PaginatedQuery[OF] = initialQuery
+	var query PaginatedQuery[Options] = initialQuery
 	for {
 		cursor, err := iterator(ctx, query)
 		if err != nil {
 			return err
 		}
 
 		if err := cb(cursor); err != nil {
 			return err
 		}
 
 		if !cursor.HasMore {
 			break
 		}
 
-		query, err = unmarshalCursor[OF](cursor.Next)
+		query, err = unmarshalCursor[Options](cursor.Next)
 		if err != nil {
 			return fmt.Errorf("paginating next request: %w", err)
 		}
 	}
 
 	return nil
 }
📝 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
func Iterate[OF any, Options any](
ctx context.Context,
initialQuery InitialPaginatedQuery[Options],
iterator func(ctx context.Context, q PaginatedQuery[Options]) (*bunpaginate.Cursor[OF], error),
cb func(cursor *bunpaginate.Cursor[OF]) error,
) error {
var query PaginatedQuery[OF] = initialQuery
for {
cursor, err := iterator(ctx, query)
if err != nil {
return err
}
if err := cb(cursor); err != nil {
return err
}
if !cursor.HasMore {
break
}
query, err = unmarshalCursor[OF](cursor.Next)
if err != nil {
return fmt.Errorf("paginating next request: %w", err)
}
}
return nil
}
func Iterate[OF any, Options any](
ctx context.Context,
initialQuery InitialPaginatedQuery[Options],
iterator func(ctx context.Context, q PaginatedQuery[Options]) (*bunpaginate.Cursor[OF], error),
cb func(cursor *bunpaginate.Cursor[OF]) error,
) error {
var query PaginatedQuery[Options] = initialQuery
for {
cursor, err := iterator(ctx, query)
if err != nil {
return err
}
if err := cb(cursor); err != nil {
return err
}
if !cursor.HasMore {
break
}
query, err = unmarshalCursor[Options](cursor.Next)
if err != nil {
return fmt.Errorf("paginating next request: %w", err)
}
}
return nil
}
🤖 Prompt for AI Agents
In internal/storage/common/cursor.go lines 72 to 101, the Iterate function has
type parameter mismatches: the variable query should be of type
PaginatedQuery[Options] to match initialQuery, and unmarshalCursor should be
called with type parameter Options instead of OF. Update the declaration of
query to use Options as its type parameter and change the unmarshalCursor call
to unmarshalCursor[Options] to ensure consistent type usage throughout the
function.

Comment on lines +23 to 28
if o.query.Offset > math.MaxInt32 {
return nil, fmt.Errorf("offset value exceeds maximum allowed value")
}
if query.Offset > 0 {
sb = sb.Offset(int(query.Offset))
if o.query.Offset > 0 {
sb = sb.Offset(int(o.query.Offset))
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix offset validation and type conversion issues.

The offset validation has two issues:

  1. The offset is of type uint64 but is being compared against math.MaxInt32, which could unnecessarily restrict valid offset values.
  2. The conversion from uint64 to int on line 27 could cause overflow if the offset exceeds the maximum int value.

Consider using math.MaxInt instead of math.MaxInt32 for the comparison, or validate against the maximum value that can be safely converted to int:

-	if o.query.Offset > math.MaxInt32 {
-		return nil, fmt.Errorf("offset value exceeds maximum allowed value")
-	}
+	if o.query.Offset > math.MaxInt {
+		return nil, fmt.Errorf("offset value exceeds maximum allowed value")
+	}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In internal/storage/common/paginator_offset.go around lines 23 to 28, the offset
validation incorrectly compares a uint64 offset against math.MaxInt32 and
converts it to int without ensuring it fits, risking overflow. Update the
validation to compare the offset against math.MaxInt (the maximum int value) or
the maximum safe int conversion limit, and only convert to int after confirming
the offset is within this safe range to prevent overflow errors.

Comment on lines +82 to 84
if o.query.Bottom == nil {
o.query.Bottom = paginationID
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid side effects in BuildCursor method.

The BuildCursor method is modifying the query's Bottom field, which is unexpected behavior. Methods that build or return data structures should generally not have side effects on their internal state.

Consider either:

  1. Making this initialization happen in the constructor or Paginate method
  2. Working with a copy of the query to avoid modifying the original
  3. Documenting this behavior if it's intentional
🤖 Prompt for AI Agents
In internal/storage/common/paginator_column.go around lines 82 to 84, the
BuildCursor method is modifying the query's Bottom field, causing unintended
side effects. To fix this, avoid changing the original query inside BuildCursor
by either initializing Bottom earlier in the constructor or Paginate method, or
by working with a copy of the query within BuildCursor so the original remains
unchanged. Alternatively, if this behavior is intentional, add clear
documentation explaining the side effect.

@gfyrag gfyrag force-pushed the refacto/pagination-system branch from d537e72 to 16362d4 Compare June 19, 2025 17:49
cursor[bot]

This comment was marked as outdated.

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: 1

♻️ Duplicate comments (1)
internal/storage/common/cursor.go (1)

72-101: Fix type parameter inconsistencies in Iterate function

This duplicates the existing review comment but the issue persists. The type parameter mismatches need to be corrected:

  1. Line 79: query should be PaginatedQuery[Options] to match initialQuery
  2. Line 94: unmarshalCursor should use Options type parameter, not OF

Apply this fix:

-	var query PaginatedQuery[OF] = initialQuery
+	var query PaginatedQuery[Options] = initialQuery
	for {
		cursor, err := iterator(ctx, query)
		if err != nil {
			return err
		}

		if err := cb(cursor); err != nil {
			return err
		}

		if !cursor.HasMore {
			break
		}

-		query, err = unmarshalCursor[OF](cursor.Next)
+		query, err = unmarshalCursor[Options](cursor.Next)
		if err != nil {
			return fmt.Errorf("paginating next request: %w", err)
		}
	}
🧹 Nitpick comments (1)
internal/storage/common/cursor.go (1)

13-13: Address the TODO comment about backporting to go-libs.

Consider creating an issue to track this backport task to avoid it being forgotten.

Do you want me to help draft an issue for tracking this backport task?

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d537e72 and 16362d4.

⛔ Files ignored due to path filters (1)
  • go.mod is excluded by !**/*.mod
📒 Files selected for processing (25)
  • internal/api/v1/controllers_accounts_list.go (2 hunks)
  • internal/api/v1/controllers_balances_list.go (2 hunks)
  • internal/api/v1/controllers_config.go (1 hunks)
  • internal/api/v1/controllers_logs_list.go (2 hunks)
  • internal/api/v1/controllers_transactions_list.go (2 hunks)
  • internal/api/v1/utils.go (1 hunks)
  • internal/api/v2/common.go (1 hunks)
  • internal/api/v2/controllers_ledgers_list.go (2 hunks)
  • internal/api/v2/controllers_ledgers_list_test.go (4 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/store.go (1 hunks)
  • internal/storage/bucket/migrations_test.go (1 hunks)
  • internal/storage/common/cursor.go (1 hunks)
  • internal/storage/common/pagination.go (1 hunks)
  • internal/storage/common/paginator_column.go (6 hunks)
  • internal/storage/common/resource.go (6 hunks)
  • internal/storage/driver/driver_test.go (2 hunks)
  • internal/storage/ledger/accounts_test.go (15 hunks)
  • internal/storage/ledger/logs_test.go (2 hunks)
  • internal/storage/ledger/store.go (2 hunks)
  • internal/storage/ledger/transactions_test.go (13 hunks)
  • internal/storage/ledger/volumes_test.go (29 hunks)
  • internal/storage/system/store.go (2 hunks)
  • internal/storage/system/store_test.go (2 hunks)
  • internal/worker/async_block.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (24)
  • internal/api/v1/controllers_balances_list.go
  • internal/api/v2/controllers_ledgers_list.go
  • internal/storage/system/store_test.go
  • internal/storage/ledger/logs_test.go
  • internal/api/v1/controllers_config.go
  • internal/api/v1/controllers_transactions_list.go
  • internal/api/v1/controllers_logs_list.go
  • internal/api/v1/controllers_accounts_list.go
  • internal/storage/ledger/transactions_test.go
  • internal/storage/bucket/migrations_test.go
  • internal/storage/ledger/volumes_test.go
  • internal/worker/async_block.go
  • internal/storage/driver/driver_test.go
  • internal/storage/ledger/accounts_test.go
  • internal/storage/system/store.go
  • internal/api/v2/controllers_ledgers_list_test.go
  • internal/storage/ledger/store.go
  • internal/api/v1/utils.go
  • internal/storage/common/pagination.go
  • internal/controller/ledger/store.go
  • internal/api/v2/common.go
  • internal/storage/common/paginator_column.go
  • internal/controller/ledger/controller_default.go
  • internal/storage/common/resource.go
🔇 Additional comments (1)
internal/storage/common/cursor.go (1)

103-108: LGTM: Simple and clean cursor encoding.

The function correctly handles nil values and delegates to the bunpaginate library.

Comment on lines +30 to +70
func unmarshalCursor[OF any](v string, modifiers ...func(query *InitialPaginatedQuery[OF]) error) (PaginatedQuery[OF], error) {
res, err := base64.RawURLEncoding.DecodeString(v)
if err != nil {
return nil, err
}

// todo: we should better rely on schema to determine the type of cursor
type aux struct {
Offset *uint64 `json:"offset"`
}
x := aux{}
if err := json.Unmarshal(res, &x); err != nil {
return nil, fmt.Errorf("invalid cursor: %w", err)
}

var q PaginatedQuery[OF]
if x.Offset != nil { // Offset defined, this is an offset cursor
q = &OffsetPaginatedQuery[OF]{}
} else {
q = &ColumnPaginatedQuery[OF]{}
}

if err := json.Unmarshal(res, &q); err != nil {
return nil, err
}

var root *InitialPaginatedQuery[OF]
if x.Offset != nil { // Offset defined, this is an offset cursor
root = &q.(*OffsetPaginatedQuery[OF]).InitialPaginatedQuery
} else {
root = &q.(*ColumnPaginatedQuery[OF]).InitialPaginatedQuery
}

for _, modifier := range modifiers {
if err := modifier(root); err != nil {
return nil, err
}
}

return reflect.ValueOf(q).Elem().Interface().(PaginatedQuery[OF]), nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor cursor type determination to avoid reflection and improve maintainability.

The current implementation has several concerning aspects:

  1. Hacky type determination: Using the presence of an offset field to determine cursor type (acknowledged in TODO on line 36)
  2. Complex reflection usage: Line 69 uses reflection to extract the interface value, which is error-prone and hard to maintain
  3. Duplicate type checking logic: Lines 46-50 and 57-61 repeat similar logic for type determination

Consider these improvements:

func unmarshalCursor[OF any](v string, modifiers ...func(query *InitialPaginatedQuery[OF]) error) (PaginatedQuery[OF], error) {
	res, err := base64.RawURLEncoding.DecodeString(v)
	if err != nil {
		return nil, err
	}

-	// todo: we should better rely on schema to determine the type of cursor
-	type aux struct {
-		Offset *uint64 `json:"offset"`
-	}
-	x := aux{}
-	if err := json.Unmarshal(res, &x); err != nil {
-		return nil, fmt.Errorf("invalid cursor: %w", err)
-	}
-
-	var q PaginatedQuery[OF]
-	if x.Offset != nil { // Offset defined, this is an offset cursor
-		q = &OffsetPaginatedQuery[OF]{}
-	} else {
-		q = &ColumnPaginatedQuery[OF]{}
-	}
-
-	if err := json.Unmarshal(res, &q); err != nil {
-		return nil, err
-	}
-
-	var root *InitialPaginatedQuery[OF]
-	if x.Offset != nil { // Offset defined, this is an offset cursor
-		root = &q.(*OffsetPaginatedQuery[OF]).InitialPaginatedQuery
-	} else {
-		root = &q.(*ColumnPaginatedQuery[OF]).InitialPaginatedQuery
-	}
-
-	for _, modifier := range modifiers {
-		if err := modifier(root); err != nil {
-			return nil, err
-		}
-	}
-
-	return reflect.ValueOf(q).Elem().Interface().(PaginatedQuery[OF]), nil
+	// Add a type discriminator field to cursors for cleaner type determination
+	type cursorEnvelope struct {
+		Type string          `json:"type"`
+		Data json.RawMessage `json:"data"`
+	}
+	
+	var envelope cursorEnvelope
+	if err := json.Unmarshal(res, &envelope); err != nil {
+		// Fallback to legacy detection for backward compatibility
+		return unmarshalLegacyCursor[OF](res, modifiers...)
+	}
+
+	switch envelope.Type {
+	case "offset":
+		var q OffsetPaginatedQuery[OF]
+		if err := json.Unmarshal(envelope.Data, &q); err != nil {
+			return nil, err
+		}
+		for _, modifier := range modifiers {
+			if err := modifier(&q.InitialPaginatedQuery); err != nil {
+				return nil, err
+			}
+		}
+		return &q, nil
+	case "column":
+		var q ColumnPaginatedQuery[OF]
+		if err := json.Unmarshal(envelope.Data, &q); err != nil {
+			return nil, err
+		}
+		for _, modifier := range modifiers {
+			if err := modifier(&q.InitialPaginatedQuery); err != nil {
+				return nil, err
+			}
+		}
+		return &q, nil
+	default:
+		return nil, fmt.Errorf("unknown cursor type: %s", envelope.Type)
+	}
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In internal/storage/common/cursor.go between lines 30 and 70, the code uses the
presence of the offset field to determine the cursor type, repeats this logic
twice, and relies on reflection to extract the interface value, which is fragile
and hard to maintain. Refactor by defining a clear interface or factory method
that encapsulates cursor type determination based on the unmarshaled data
schema, eliminate duplicate type checks by consolidating the logic into one
place, and remove reflection by directly returning the concrete PaginatedQuery
implementation without using reflect.ValueOf or type assertions that require
reflection.

Copy link

codecov bot commented Jun 19, 2025

Codecov Report

Attention: Patch coverage is 88.32998% with 58 lines in your changes missing coverage. Please review.

Project coverage is 82.92%. Comparing base (539800c) to head (858712a).

Files with missing lines Patch % Lines
internal/storage/common/cursor.go 70.31% 14 Missing and 5 partials ⚠️
internal/storage/common/schema.go 85.95% 14 Missing and 3 partials ⚠️
internal/storage/common/resource.go 84.21% 7 Missing and 2 partials ⚠️
internal/api/v1/utils.go 82.85% 4 Missing and 2 partials ⚠️
internal/api/v2/controllers_volumes.go 91.42% 2 Missing and 1 partial ⚠️
internal/storage/common/paginator_offset.go 89.47% 0 Missing and 2 partials ⚠️
internal/storage/common/pagination.go 0.00% 1 Missing ⚠️
internal/storage/common/paginator_column.go 97.29% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #978      +/-   ##
==========================================
- Coverage   82.98%   82.92%   -0.07%     
==========================================
  Files         142      145       +3     
  Lines        8089     8199     +110     
==========================================
+ Hits         6713     6799      +86     
- Misses       1057     1075      +18     
- Partials      319      325       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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: 1

🧹 Nitpick comments (1)
test/e2e/v1_api_accounts_list_test.go (1)

53-53: Add error checking for SetString operation

While the number string is valid and unlikely to fail, it's good practice to check for errors when using SetString.

-bigInt, _ = big.NewInt(0).SetString("999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10)
+bigInt, ok := big.NewInt(0).SetString("999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10)
+Expect(ok).To(BeTrue())
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16362d4 and 2d27b42.

⛔ Files ignored due to path filters (3)
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v1.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (6)
  • internal/api/v2/controllers_ledgers_list.go (1 hunks)
  • internal/controller/ledger/mocks_test.go (4 hunks)
  • pkg/client/v1.go (1 hunks)
  • test/e2e/v1_api_accounts_list_test.go (1 hunks)
  • test/e2e/v1_api_logs_list_test.go (1 hunks)
  • test/e2e/v1_api_transactions_list_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/api/v2/controllers_ledgers_list.go
  • internal/controller/ledger/mocks_test.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Tests
  • GitHub Check: Dirty
🔇 Additional comments (7)
pkg/client/v1.go (1)

614-614: ```shell
#!/bin/bash

Extract CountAccounts method body to inspect HTTP method and status code checks

echo "=== CountAccounts snippet (lines 450–650) ==="
sed -n '450,650p' pkg/client/v1.go

Extract CountTransactions method body to inspect HTTP method and status code checks

echo -e "\n=== CountTransactions snippet (lines 2100–2330) ==="
sed -n '2100,2330p' pkg/client/v1.go


</details>
<details>
<summary>test/e2e/v1_api_logs_list_test.go (1)</summary>

`1-328`: **Well-structured end-to-end tests for logs listing**

The test suite provides comprehensive coverage for the logs listing API, including:
- Basic listing and content validation
- Pagination with forward and backward cursor navigation
- Proper assertions for log types and data structure

The tests are well-organized using nested contexts and descriptive test names.

</details>
<details>
<summary>test/e2e/v1_api_accounts_list_test.go (1)</summary>

`26-305`: **Comprehensive test coverage for accounts listing API**

The test suite provides excellent coverage for the accounts listing API, including:
- Account counting and listing
- Filtering by address patterns and metadata
- Empty ledger scenarios
- Pagination with cursor navigation

The tests follow the same structure as the logs tests, maintaining consistency across the test suite.

</details>
<details>
<summary>test/e2e/v1_api_transactions_list_test.go (4)</summary>

`1-21`: **LGTM!**

The imports and build constraints are well-structured for an integration test.

---

`23-43`: **LGTM!**

The test setup properly initializes the database, test server, and creates the default ledger before each test.

---

`91-94`: **LGTM!**

The transaction prepending logic correctly builds the transactions slice in reverse chronological order (newest first), which aligns with typical API pagination expectations.

---

`99-202`: **LGTM!**

The pagination test cases are comprehensive and well-structured:
- Tests different page sizes (5 and 10)
- Tests cursor-based navigation (next/previous)
- Tests filtering by reference and other parameters
- Properly validates page sizes, data content, and cursor behavior

The test logic correctly validates both forward and backward pagination scenarios.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +57 to +61
if offset%2 == 0 {
offset += 1
} else {
offset -= 1
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the timestamp alternation logic.

The current logic uses modulo on a time.Duration type, which doesn't achieve the intended alternating pattern described in the comment. The modulo operation on Duration (nanoseconds) doesn't provide meaningful alternation for testing purposes.

Apply this diff to fix the alternating timestamp logic:

-				// 1 transaction of 2 is backdated to test pagination using effective date
-				if offset%2 == 0 {
-					offset += 1
-				} else {
-					offset -= 1
-				}
+				// 1 transaction of 2 is backdated to test pagination using effective date
+				if i%2 == 0 {
+					offset += time.Nanosecond
+				} else {
+					offset -= time.Nanosecond
+				}
🤖 Prompt for AI Agents
In test/e2e/v1_api_transactions_list_test.go around lines 57 to 61, the current
code incorrectly uses modulo on a time.Duration type to alternate timestamps,
which does not produce the intended alternating pattern. Replace the modulo
operation on the Duration with a modulo on an integer counter or index variable
that tracks iterations, and use that to alternate the timestamp by adding or
subtracting a fixed duration accordingly.

@gfyrag gfyrag force-pushed the refacto/pagination-system branch from 2d27b42 to 858712a Compare June 20, 2025 07:30
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: 0

♻️ Duplicate comments (1)
test/e2e/v1_api_transactions_list_test.go (1)

57-61: Fix the timestamp alternation logic.

The current logic uses modulo on a time.Duration type, which doesn't achieve the intended alternating pattern described in the comment. The modulo operation on Duration (nanoseconds) doesn't provide meaningful alternation for testing purposes.

🧹 Nitpick comments (1)
test/e2e/v1_api_transactions_list_test.go (1)

91-94: Consider minor optimization for transaction collection.

The current prepending pattern has O(n²) complexity as it copies the entire slice on each iteration. While acceptable for test purposes with 20 transactions, consider using a simple append and reversing the slice afterward for better performance in larger test scenarios.

-				transactions = append([]components.Transaction{
-					response.TransactionsResponse.Data[0],
-				}, transactions...)
+				transactions = append(transactions, response.TransactionsResponse.Data[0])

Then reverse the slice after the loop if needed for test ordering expectations.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d27b42 and 858712a.

⛔ Files ignored due to path filters (3)
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v1.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (7)
  • internal/api/v2/controllers_ledgers_list.go (1 hunks)
  • internal/controller/ledger/mocks_test.go (4 hunks)
  • internal/storage/common/paginator_offset.go (2 hunks)
  • pkg/client/v1.go (1 hunks)
  • test/e2e/v1_api_accounts_list_test.go (1 hunks)
  • test/e2e/v1_api_logs_list_test.go (1 hunks)
  • test/e2e/v1_api_transactions_list_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • internal/api/v2/controllers_ledgers_list.go
  • test/e2e/v1_api_logs_list_test.go
  • test/e2e/v1_api_accounts_list_test.go
  • pkg/client/v1.go
  • internal/storage/common/paginator_offset.go
  • internal/controller/ledger/mocks_test.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Tests
  • GitHub Check: Dirty
🔇 Additional comments (4)
test/e2e/v1_api_transactions_list_test.go (4)

1-21: LGTM! Standard e2e test setup.

The package declaration, build tags, and imports are well-organized and appropriate for an end-to-end integration test.


23-43: Well-structured test setup.

The test context setup properly initializes the database, test server, and creates the required ledger. Good use of Ginkgo patterns and proper resource management.


99-130: Excellent pagination test coverage.

This test effectively validates that pagination cursors work correctly when page sizes change between requests, which is an important edge case to verify.


131-202: Comprehensive pagination and filtering test suite.

This test section provides excellent coverage of:

  • Pagination with various filters (source, reference, time range)
  • Cursor navigation (forward and backward)
  • Pagination metadata validation
  • Edge cases like end-of-results scenarios

The test logic correctly handles the transaction ordering created by the prepending pattern in the setup.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Bug: API Change Breaks Count Endpoint

The count accounts endpoint's HTTP status code was changed from 200 (OK) to 204 (No Content). This is a breaking API change for existing clients that expect a 200 OK response. While 204 might be technically correct as the count is returned in headers, count operations typically return 200. This change is reflected in the OpenAPI specification and client-side response handling.

openapi/v1.yaml#L92-L93

ledger/openapi/v1.yaml

Lines 92 to 93 in 858712a

responses:
'204':

pkg/client/v1.go#L613-L614

ledger/pkg/client/v1.go

Lines 613 to 614 in 858712a

switch {
case httpRes.StatusCode == 204:

Fix in Cursor


Was this report helpful? Give feedback by reacting with 👍 or 👎

@gfyrag gfyrag changed the title refacto: pagination system refactor: pagination system Jun 20, 2025
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