Skip to content

refactor(ergo-api): split server.rs, types.rs, batch.rs (1/2) - #216

Merged
arkadianet merged 5 commits into
mainfrom
refactor/ergo-api-split-1
Jul 18, 2026
Merged

refactor(ergo-api): split server.rs, types.rs, batch.rs (1/2)#216
arkadianet merged 5 commits into
mainfrom
refactor/ergo-api-split-1

Conversation

@arkadianet

@arkadianet arkadianet commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Mechanical, behavior-preserving file-split refactor of the three largest ergo-api root/route files. First of a 2-PR stack (part 2: refactor/ergo-api-split, splitting wallet/native/dto.rs, v1/routes/dto.rs, v1/routes/tx_intel.rs). One commit per split file; every function body moved verbatim (only import headers, new-file module docs, and the minimum visibility promotions listed below).

Split map

src/server.rs (2,524 lines) → src/server/

  • mod.rsServerCtx, bind/serve wrappers (bind, serve_on*, serve, realtime_handle), the utxo_reads_supported-gated subtree (scala_utxo_subtree + 503 handler + snapshots-info handler), all router builders incl. the ~920-line router_with_mempool_and_wallet_and_security, wallet-ui redirects, SPA security headers
  • handlers.rs — the 18 native operator handlers (info/identity/host/status/votes/set_votes/tip/sync/peers/recent_blocks/events/difficulty_history/miner_stats/votes_history/metrics/shutdown/peers_connect/health)
  • shared.rssubmit_via_node + map_submit_error (already pub(crate); re-exported at crate::server::* so compat::{blocks,transactions} call sites are untouched)
  • openapi.rsNativeOpenApi derive + SecurityAddon + native_openapi_yaml + the cfg(test) emission test
  • assets.rs — static-asset handlers (index, swagger pages, spec emitters, fonts, css, js helper)

src/types.rs (1,876 lines) → src/types/

  • mod.rs (mod decls + glob re-exports + shared hex32), identity.rs, status.rs, voting.rs, chain_refs.rs, peers.rs, mempool.rs, events.rs, indexer.rs — flat DTO list partitioned by domain; the ~700-line wire-shape/round-trip pinning-test tail was split alongside each owning domain file (no monolithic test file left behind). ApiWeightFunction's real TryFrom<&str> + error type and ApiNativeSubmitError's From<ApiSubmitError> moved verbatim.

src/v1/routes/batch.rs (1,125 lines) → src/v1/routes/batch/

  • mod.rs — module doc, public policy caps (MAX_BATCH_ITEMS, MAX_BATCH_WEIGHT), wire types (BatchRequest/BatchItemRequest/BatchItemResult/BatchResponse)
  • allowlist.rs — the route! macro and the ~500-line allowed_routes() manifest kept together as one self-contained unit, so the macro's dispatch-router/classification-table anti-drift invariant is preserved structurally (dispatch.rs consumes only the resulting data, never the macro); the existing table-invariant test (allowed_routes_table_excludes_mutating_submit_domain) lives here and passes
  • dispatch.rsAllowedRoute, template_matches/specificity/classify, BatchState, parse_method, dispatch_one, batch_handler, batch_router

Honored "do NOT split" call-outs

  • router_with_mempool_and_wallet_and_security kept whole in server/mod.rs
  • batch dispatch/handler logic kept together as one unit in dispatch.rs
  • route! macro + table kept together in allowlist.rs (spec's preferred option)

Visibility promotions (complete list, with reasons)

  • server/handlers.rs: 18 handler fns private → pub(super) (used by mod.rs route registration and by the NativeOpenApi paths() derive)
  • server/assets.rs: 15 asset fns private → pub(super) (used by mod.rs route registration)
  • batch/dispatch.rs: AllowedRoute struct + its 3 fields private → pub(super) (constructed by allowlist.rs's route! table)
  • batch/allowlist.rs: allowed_routes() private → pub(super) (called by batch_router)
  • mod dispatch is pub(crate): v1/openapi.rs's paths() entry was re-pathed to batch::dispatch::batch_handler because utoipa resolves the hidden __path_* struct beside the fn definition, which a plain re-export does not carry
  • Everything else keeps its original visibility (submit_via_node/map_submit_error stay pub(crate), all types DTOs were already pub)

Verification

  • Baseline (origin/main 981c5a0): cargo test -p ergo-api = 1041 passed / 0 failed / 5 ignored (66 targets); clippy clean
  • After every split and at this head: 1041 / 0 / 5 — identical
  • OpenAPI golden fixtures (openapi_native_snapshot, openapi_v1_snapshot) pass unchanged — the generated specs are byte-identical
  • cargo clippy -p ergo-api --all-targets --all-features -- -D warnings clean; cargo fmt --all --check clean; cargo check --workspace --all-targets clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kw7uCwqmiGna8fpg6TvxGi

Summary by CodeRabbit

  • New Features
    • Added operator dashboard APIs for node status, health, peers, events, voting, chain data, mempool activity, and metrics.
    • Added OpenAPI and Swagger documentation endpoints, including native and v1 specifications.
    • Added embedded dashboard assets, fonts, JavaScript, and styles.
    • Added a read-only batch API supporting up to 32 requests per call.
    • Added administrative actions for peer connections, voting updates, and node shutdown.
  • Documentation
    • Expanded generated API documentation to include batch dispatch routes and authentication details.

arkadianet and others added 3 commits July 17, 2026 20:23
Mechanical, behavior-preserving file split of the 2,524-line
ergo-api/src/server.rs into a server/ directory module:

- server/mod.rs      — ServerCtx, bind/serve wrappers (bind, serve_on*,
                       serve, realtime_handle), the utxo_reads_supported
                       gated subtree (scala_utxo_subtree,
                       utxo_lookup_unsupported_in_digest_mode,
                       scala_utxo_snapshots_info_handler), all router
                       builders incl. the master
                       router_with_mempool_and_wallet_and_security,
                       wallet_ui redirects, SPA security headers
- server/handlers.rs — the 18 native operator handlers (info, identity,
                       host, status, votes, set_votes, tip, sync, peers,
                       recent_blocks, events, difficulty_history,
                       miner_stats, votes_history, metrics, shutdown,
                       peers_connect, health)
- server/shared.rs   — submit_via_node + map_submit_error (already
                       pub(crate); re-exported at crate::server::* so the
                       compat::{blocks,transactions} call sites are
                       untouched)
- server/openapi.rs  — NativeOpenApi derive, SecurityAddon + Modify impl,
                       native_openapi_yaml, and the cfg(test) OpenAPI
                       emission test
- server/assets.rs   — static-asset handlers (index, swagger*, openapi
                       yaml/json emitters, fonts, css, js helper)

All function bodies moved verbatim. The only non-move edits:
- import headers per file + module docs for the new files
- NativeOpenApi paths() entries re-pathed to super::handlers::* (the
  handlers now live in a sibling module)
- visibility promotions, all private -> pub(super), for true cross-file
  entry points only: the 18 handler fns and the 15 asset fns (used by
  mod.rs route registration and, for handlers, the NativeOpenApi derive)

Public/crate surface unchanged: crate::server::{bind, realtime_handle,
serve*, router*, ServerCtx, native_openapi_yaml, NativeOpenApi,
submit_via_node, map_submit_error} all resolve as before via mod.rs
re-exports.

Verification: cargo test -p ergo-api 1041 passed / 0 failed / 5 ignored
(identical to baseline); openapi_native_matches_snapshot golden held;
cargo clippy -p ergo-api --all-targets --all-features -D warnings clean;
cargo fmt clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kw7uCwqmiGna8fpg6TvxGi
Mechanical, behavior-preserving file split of the 1,876-line
ergo-api/src/types.rs (flat wire-DTO list + pinning-test tail) into a
types/ directory module organized by domain:

- types/mod.rs        — module doc, private mod decls + glob re-exports
                        (surface stays ergo_api::types::X exactly), and
                        the shared hex32() helper (used by ergo-node's
                        snapshot builder via the crate-root glob)
- types/identity.rs   — ApiInfo (+default_block_interval_ms), ApiIdentity,
                        ApiStateType, ApiHistoryMode, ApiHost
- types/status.rs     — ApiStatus, ApiBlockApplyError, ApiSyncWedged,
                        ApiShadowStatus/Divergence, ApiBootstrapStatus/
                        Phase, ApiPopowPhase, ApiHeaderAvailability,
                        SyncStateLabel, ApiSyncStatus, ApiHealth,
                        HealthStatus
- types/voting.rs     — ApiVotes, ApiVotableParam, ApiConfiguredVote,
                        ApiSetVotesRequest, ApiVoteTarget, ApiVotesHistory,
                        ApiVoteChangeEvent, ApiParamChange
- types/chain_refs.rs — ApiTip, ApiHeaderRef, ApiFullBlockRef,
                        ApiRecentBlock, ApiDifficultyPoint/Series,
                        ApiMinerStat/Stats
- types/peers.rs      — ApiPeer, ApiPeerDirection, ApiPeerState
- types/mempool.rs    — ApiMempoolSummary/Transactions/Transaction,
                        ApiIoBox, ApiAsset, ApiTxDetail, ApiWeightFunction
                        (+UnknownWeightFunction error + TryFrom<&str>,
                        moved verbatim), ApiTxSource, SubmitMode,
                        SubmitError, ApiSubmitResponse, ApiSubmitError,
                        ApiNativeSubmitError (+From<ApiSubmitError>, moved
                        verbatim), RawTransactionBytes
- types/events.rs     — ApiNodeEvent/Events, ApiReorgRecord/History
- types/indexer.rs    — ApiIndexerRepair/Totals/Status

The ~700-line wire-shape/round-trip pinning-test tail is partitioned
alongside its owning domain file (identity/status/chain_refs/peers/
mempool/events); every test moved verbatim, so the wire-format pins
themselves prove serde attributes and field order are unchanged.

No visibility promotions: every DTO was already pub, hex32 stays pub,
default_block_interval_ms stays private beside ApiInfo. No cross-file
imports were needed (each domain's field types land in the same file).

Verification: cargo test -p ergo-api 1041 passed / 0 failed / 5 ignored
(identical to baseline, all pinning tests included); clippy --all-targets
--all-features -D warnings clean; fmt clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kw7uCwqmiGna8fpg6TvxGi
Mechanical, behavior-preserving file split of the 1,125-line
ergo-api/src/v1/routes/batch.rs:

- batch/mod.rs       — module doc, the two public policy caps
                       (MAX_BATCH_ITEMS, MAX_BATCH_WEIGHT), and the wire
                       types (BatchRequest, BatchItemRequest,
                       BatchItemResult + its ok/error constructors,
                       BatchResponse); re-exports dispatch::batch_router
                       so crate::v1::routes::batch::batch_router (and the
                       chain up to crate::v1::batch_router) is unchanged
- batch/allowlist.rs — the route! macro and the ~500-line allowed_routes()
                       manifest, kept together as one self-contained unit
                       (the macro registers each entry on BOTH the
                       dispatch Router and the classification table, the
                       anti-drift invariant), plus the
                       allowed_routes_table_excludes_mutating_submit_domain
                       test
- batch/dispatch.rs  — AllowedRoute, template_matches, specificity,
                       classify, BatchState, parse_method, dispatch_one,
                       batch_handler, batch_router, the private buffering
                       caps (MAX_BUFFERED_BODY_BYTES, UNKNOWN_IP), and the
                       template/classify/parse_method tests

All function bodies moved verbatim. Non-move edits:
- import headers + module docs for the new files
- v1/openapi.rs paths() entry re-pathed
  crate::v1::routes::batch::batch_handler ->
  crate::v1::routes::batch::dispatch::batch_handler (utoipa resolves the
  hidden __path struct beside the fn definition, which a plain re-export
  does not carry; dispatch is pub(crate) for exactly this reference)

Visibility promotions (minimum set):
- AllowedRoute struct + its three fields: private -> pub(super)
  (constructed by allowlist.rs's route! table, consumed by dispatch.rs)
- allowed_routes(): private -> pub(super) (called by batch_router in
  dispatch.rs)
- mod dispatch is pub(crate) (openapi.rs derive reference above)

Verification: cargo test -p ergo-api 1041 passed / 0 failed / 5 ignored
(identical to baseline); openapi v1 + native golden snapshots held;
clippy --all-targets --all-features -D warnings clean; fmt clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kw7uCwqmiGna8fpg6TvxGi
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@arkadianet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40ee042e-b111-4d72-b3d8-5bd59ca403eb

📥 Commits

Reviewing files that changed from the base of the PR and between b276c17 and 4f9ce63.

📒 Files selected for processing (2)
  • ergo-api/src/types/voting.rs
  • ergo-api/tests/fixtures/openapi_native.yaml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/ergo-api-split-1

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
ergo-api/src/v1/routes/batch/dispatch.rs (1)

96-102: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Ignore query strings during route classification.

If a client includes a query string in the path field instead of using the query field, it currently results in an inconsistency. A path like /api/v1/boxes/abcd?limit=10 will successfully pass classification (as abcd?limit=10 satisfies the wildcard :box_id check) and route correctly. However, a path like /api/v1/boxes?limit=10 will fail classification because "boxes?limit=10" does not literally match "boxes", resulting in a ForbiddenTarget error.

Stripping the query string during classification resolves this inconsistency and safely allows Axum to parse and route the URI correctly in both scenarios.

♻️ Proposed refactor
 fn classify(table: &[AllowedRoute], method: &Method, path: &str) -> Option<RouteClass> {
+    let path_only = path.split_once('?').map_or(path, |(p, _)| p);
     table
         .iter()
-        .filter(|r| r.method == *method && template_matches(r.template, path))
+        .filter(|r| r.method == *method && template_matches(r.template, path_only))
         .max_by_key(|r| specificity(r.template))
         .map(|r| r.class)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ergo-api/src/v1/routes/batch/dispatch.rs` around lines 96 - 102, Update
classify to remove the query-string suffix from path before calling
template_matches and specificity, while preserving the existing method filtering
and route-selection behavior. Use only the portion before the first '?' so paths
with query parameters classify consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ergo-api/src/types/voting.rs`:
- Line 79: Update the voting cadence documentation near the “Desired value”
comment to state that the node votes one step per voting epoch, matching the
behavior described in the surrounding voting documentation. Do not describe the
cadence as occurring per block.

---

Nitpick comments:
In `@ergo-api/src/v1/routes/batch/dispatch.rs`:
- Around line 96-102: Update classify to remove the query-string suffix from
path before calling template_matches and specificity, while preserving the
existing method filtering and route-selection behavior. Use only the portion
before the first '?' so paths with query parameters classify consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79a26637-da06-4c1f-978b-6f9b68eab737

📥 Commits

Reviewing files that changed from the base of the PR and between 981c5a0 and b276c17.

📒 Files selected for processing (20)
  • ergo-api/src/server/assets.rs
  • ergo-api/src/server/handlers.rs
  • ergo-api/src/server/mod.rs
  • ergo-api/src/server/openapi.rs
  • ergo-api/src/server/shared.rs
  • ergo-api/src/types.rs
  • ergo-api/src/types/chain_refs.rs
  • ergo-api/src/types/events.rs
  • ergo-api/src/types/identity.rs
  • ergo-api/src/types/indexer.rs
  • ergo-api/src/types/mempool.rs
  • ergo-api/src/types/mod.rs
  • ergo-api/src/types/peers.rs
  • ergo-api/src/types/status.rs
  • ergo-api/src/types/voting.rs
  • ergo-api/src/v1/openapi.rs
  • ergo-api/src/v1/routes/batch.rs
  • ergo-api/src/v1/routes/batch/allowlist.rs
  • ergo-api/src/v1/routes/batch/dispatch.rs
  • ergo-api/src/v1/routes/batch/mod.rs
💤 Files with no reviewable changes (2)
  • ergo-api/src/types.rs
  • ergo-api/src/v1/routes/batch.rs

Comment thread ergo-api/src/types/voting.rs Outdated
arkadianet and others added 2 commits July 19, 2026 02:27
… block

The `target` doc said "one step per block", contradicting `ApiVotableParam`
(one step per voting epoch) and the per-epoch history rows. Ergo advances a
votable parameter at most one step per voting epoch (1024 blocks on mainnet),
not per block; correct the comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kw7uCwqmiGna8fpg6TvxGi
… fix

The `ApiVoteTarget.target` doc-comment change (per-block → per-voting-epoch)
flows into the utoipa `ToSchema` `description`, so the native OpenAPI snapshot
drifted. Regenerate the checked-in golden fixture to match; no schema shape
change, only the corrected description text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kw7uCwqmiGna8fpg6TvxGi
@arkadianet
arkadianet marked this pull request as ready for review July 18, 2026 17:37
@arkadianet
arkadianet merged commit e41d54a into main Jul 18, 2026
9 checks passed
@arkadianet
arkadianet deleted the refactor/ergo-api-split-1 branch July 18, 2026 17:37
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.

1 participant