Skip to content

Shared RaindexClient: eliminate per-request thread spawning#58

Merged
findolor merged 7 commits intomainfrom
shared-raindex-client
Mar 5, 2026
Merged

Shared RaindexClient: eliminate per-request thread spawning#58
findolor merged 7 commits intomainfrom
shared-raindex-client

Conversation

@findolor
Copy link
Collaborator

@findolor findolor commented Mar 3, 2026

Dependent PRs

Motivation

Every API request previously spawned a new OS thread + Tokio runtime to create a fresh RaindexClient via run_with_client(). This was necessary because the old RaindexClient used Rc/RefCell internally and wasn't Send+Sync.

The upstream rain.orderbook now makes RaindexClient Send+Sync on native (uses Arc<Mutex<...>>), and new_native() starts the sync scheduler internally. This lets us create a single shared client at startup and pass it to all handlers via Rocket state.

Solution

  • Bump submodule to 5c78970c25 which provides Send+Sync RaindexClient
  • Rewrite RaindexProvider to hold a shared RaindexClient created at startup. Thread spawning is kept only for construction (DotrainRegistry future is not Send due to JsValue)
  • Gut sync.rs — sync is now handled internally by RaindexClient::new_native()
  • Make data source traits Send+Sync — change async_trait(?Send) to async_trait on OrderDataSource and SwapDataSource
  • Simplify all route handlers — replace run_with_client pattern with direct raindex.client() access
  • Update tests — remove tests for per-request client init failures (no longer possible)

Net result: -401 lines, +115 lines across 18 files. Local benchmarks show ~2s response times (quote/calldata), consistent with local DB performance.

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • Refactor

    • Consolidated orderbook client initialization and simplified how services use the shared client.
  • Chores

    • Enforced thread-safety for data-source interfaces and updated tests to match the new initialization pattern.
  • Behavior

    • Several endpoints and flows are temporarily replaced with placeholders (work in progress); client-backed operations are currently unimplemented.
    • Background/native sync entry removed.

findolor added 5 commits March 3, 2026 17:05
RaindexClient is now Send+Sync on native (uses Arc<Mutex<...>>
internally) and new_native() starts the sync scheduler internally.
Replace per-request thread spawning with a single client created at
startup. Thread spawning is kept only for construction (DotrainRegistry
future is not Send due to JsValue). Sync is now handled internally by
RaindexClient::new_native(), so sync.rs is gutted.
Change async_trait(?Send) to async_trait and add Send + Sync bounds
on OrderDataSource and SwapDataSource traits, since RaindexClient is
now Send+Sync and handlers no longer need non-Send futures.
Replace run_with_client pattern with direct raindex.client() access
in all route handlers. Remove per-request thread spawning overhead.
Remove tests for per-request client init failures (no longer possible
since client is created eagerly at startup). Update mock helpers.
@findolor findolor self-assigned this Mar 3, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 284dc2d0-7e9e-4b43-9302-e1453ddcbafc

📥 Commits

Reviewing files that changed from the base of the PR and between 707dc4d and d507eab.

📒 Files selected for processing (2)
  • src/main.rs
  • src/sync.rs
💤 Files with no reviewable changes (1)
  • src/sync.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main.rs

📝 Walkthrough

Walkthrough

RaindexProvider now initializes and owns a RaindexClient via load(registry_url, Option); run_with_client and set_db_path/start_sync helpers were removed. Routes and data-source traits switched to use provider.client() directly and now require Send + Sync; several handlers replaced with todo!() placeholders.

Changes

Cohort / File(s) Summary
Submodule Update
lib/rain.orderbook
Submodule pointer bumped to a new commit; no behavioral changes in this diff.
Raindex provider core
src/raindex/config.rs
Provider now owns client: RaindexClient; load signature changed to load(registry_url: &str, db_path: Option<PathBuf>); removed set_db_path and run_with_client; added client() and db_path() accessors; simplified error variants (RegistryLoad, ClientInit, WorkerPanicked).
Bootstrap / tests
src/main.rs, src/test_helpers.rs
Updated calls to RaindexProvider::load(..., db_path) (tests pass None); removed mock_invalid_raindex_config helper and intermediate db_path wiring in main.
Trait bounds (Order/Swap)
src/routes/order/mod.rs, src/routes/swap/mod.rs
Added : Send + Sync to OrderDataSource and SwapDataSource; changed #[async_trait(?Send)]#[async_trait] on traits and implementations.
Order routes
src/routes/order/cancel.rs, src/routes/order/get_order.rs, src/routes/order/deploy_dca.rs, src/routes/order/deploy_solver.rs
Replaced run_with_client usage with direct raindex.client() constructions; several handlers (deploy_dca, deploy_solver) now contain todo!() placeholders; error mapping simplified/removed in places.
Swap routes
src/routes/swap/calldata.rs, src/routes/swap/quote.rs, src/routes/swap/mod.rs
Construct RaindexSwapDataSource from owned raindex.client() (owned vs. borrowed); removed run_with_client wrappers; tests for client-init failure removed.
Admin, orders, trades, tests
src/routes/admin.rs, src/routes/orders.rs, src/routes/trades.rs, src/routes/tests.rs
Admin route now passes db_path into RaindexProvider::load; many endpoints that previously used run_with_client now acquire the lock then call todo!(); test contract renamed/updated (run_with_client_contractshared_client_contract).
Sync surface
src/sync.rs
Removed public start_sync(...) and its tests; sync now handled via new client/runtime in provider (no public start_sync).

Sequence Diagram(s)

sequenceDiagram
    participant Client as "HTTP Route Handler"
    participant Provider as "RaindexProvider"
    participant RaindexClient as "RaindexClient / Orderbook"

    rect rgba(200,230,255,0.5)
    Client->>Provider: read State / get reference
    Provider->>Provider: .client() (owned client accessor)
    end

    rect rgba(200,255,200,0.5)
    Client->>RaindexClient: call process_* (e.g., process_get_order)
    RaindexClient-->>Client: result / error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • 0xgleb
  • hardyjosh

Poem

🐇 A hop, a tweak, the client tucked inside,

I share my paw — the provider is my guide,
Send and Sync stitched snug for threads that play,
run_with_client hopped off to fields of hay,
A rabbit cheers the tidy, threaded tide.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Shared RaindexClient: eliminate per-request thread spawning' accurately and concisely describes the main architectural change - moving from per-request client creation with thread spawning to a single shared client initialized at startup.
Docstring Coverage ✅ Passed Docstring coverage is 94.29% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch shared-raindex-client

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

❤️ Share

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

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.rs`:
- Around line 237-242: Before calling raindex::RaindexProvider::load (the block
that assigns raindex_config), validate local_db_path: ensure it is either an
absolute path or includes a directory component (i.e.,
Path::new(&local_db_path).is_absolute() ||
Path::new(&local_db_path).parent().and_then(|p| p.to_str()).filter(|s|
!s.is_empty()).is_some()). If the check fails (bare filename like "local.db"),
return or propagate an error / log and exit prior to invoking
RaindexProvider::load so the code does not forward invalid paths; reference the
raindex_config assignment, the local_db_path variable, and
raindex::RaindexProvider::load when adding the guard.

In `@src/raindex/config.rs`:
- Around line 14-20: The load function should reject bare filenames for db_path
at load-time: in pub(crate) async fn load(...), if let Some(db) =
db_path.clone() { check db.parent() and ensure it is Some and not empty (i.e.,
path has a directory prefix like "./data/local.db" or "db/local.db"); if parent
is None or empty, return an Err(RaindexProviderError::InvalidConfig(...)) (or
add a new RaindexProviderError variant) with a clear message so consumers cannot
pass bare "local.db". Implement this validation early in load before using db.

In `@src/routes/order/deploy_dca.rs`:
- Around line 34-35: The handler currently calls todo!() after acquiring
_raindex (shared_raindex.read().await) which panics and bypasses the ApiError
contract; replace the todo!() with proper non-panicking error handling that
returns a Result with an ApiError variant (e.g.,
Err(ApiError::InternalServerError or a more specific ApiError::BadRequest)
rather than panicking), ensure the function (deploy_dca handler) returns the
expected Result type, and propagate or map any errors from shared_raindex access
or subsequent logic into ApiError so all failures go through the ApiError enum.

In `@src/routes/order/deploy_solver.rs`:
- Around line 34-35: The handler currently calls todo!() (after awaiting
shared_raindex.read().await) which panics on every /v1/order/solver request;
replace the panic with a proper ApiError return so the error flows through typed
API error handling. Specifically, remove todo!() and return an
Err(ApiError::...) from the handler (e.g., ApiError::Internal or a more specific
variant like ServiceUnavailable) with a clear message such as "solver endpoint
not implemented" and include any context you want from shared_raindex; ensure
the function signature (the route handler that awaited
shared_raindex.read().await) still returns a Result so the Err variant is used
instead of panicking.

In `@src/routes/orders.rs`:
- Around line 37-38: The handlers currently call todo!() after acquiring the
read lock on shared_raindex (let _raindex = shared_raindex.read().await) which
panics and breaks the endpoints; replace todo!() in both order handlers with
real logic that uses the acquired read guard (use the variable name e.g.,
raindex) to perform the lookup/operation and return Result<..., ApiError>,
mapping any failure to the appropriate ApiError variant instead of panicking or
returning raw status codes; ensure all error paths convert errors into ApiError
(via ? or map_err) and that the handler signatures return the ApiError-based
Response type so the endpoints return typed API errors rather than panics.

In `@src/routes/trades.rs`:
- Around line 37-38: The handlers for /v1/trades/tx/{tx_hash} and
/v1/trades/{address} currently call todo!() and thus panic; replace the todo!()
in the blocks that acquire shared_raindex.read().await with real logic that
queries the raindex and returns results or an ApiError variant instead of
panicking. Specifically, in the function(s) handling tx_hash and address use the
read guard from shared_raindex to perform the lookup, map a missing result to
the appropriate ApiError (e.g., ApiError::NotFound or the project's canonical
error variant) and map any internal errors to ApiError::Internal or
ApiError::ServiceUnavailable as appropriate, then return Ok(response) on
success; ensure every failure path returns Err(ApiError::...) so no raw status
codes or panics remain.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6e8108 and 83d7dfb.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • lib/rain.orderbook
  • src/main.rs
  • src/raindex/config.rs
  • src/routes/admin.rs
  • src/routes/order/cancel.rs
  • src/routes/order/deploy_dca.rs
  • src/routes/order/deploy_solver.rs
  • src/routes/order/get_order.rs
  • src/routes/order/mod.rs
  • src/routes/orders.rs
  • src/routes/swap/calldata.rs
  • src/routes/swap/mod.rs
  • src/routes/swap/quote.rs
  • src/routes/tests.rs
  • src/routes/trades.rs
  • src/sync.rs
  • src/test_helpers.rs

Comment on lines +14 to +20
pub(crate) async fn load(
registry_url: &str,
db_path: Option<PathBuf>,
) -> Result<Self, RaindexProviderError> {
let url = registry_url.to_string();
let (tx, rx) = tokio::sync::oneshot::channel::<Result<DotrainRegistry, WorkerError>>();
let db = db_path.clone();

Copy link

@coderabbitai coderabbitai bot Mar 3, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate db_path format at load-time.

Line [16] currently accepts bare filenames (for example local.db), which breaks this repo’s configuration-path convention.

Suggested validation guard
 impl RaindexProvider {
     pub(crate) async fn load(
         registry_url: &str,
         db_path: Option<PathBuf>,
     ) -> Result<Self, RaindexProviderError> {
+        if let Some(path) = db_path.as_ref() {
+            let has_parent_dir = path
+                .parent()
+                .is_some_and(|parent| !parent.as_os_str().is_empty());
+            if !has_parent_dir {
+                return Err(RaindexProviderError::ClientInit(
+                    "db_path must include a directory prefix (e.g., data/raindex.db)".into(),
+                ));
+            }
+        }
+
         let url = registry_url.to_string();
         let db = db_path.clone();

Based on learnings: "In ST0x-Technology/st0x.rest.api, Rust source files that deal with configuration should specify database paths with a directory prefix ... never use bare filenames like local.db."

📝 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
pub(crate) async fn load(
registry_url: &str,
db_path: Option<PathBuf>,
) -> Result<Self, RaindexProviderError> {
let url = registry_url.to_string();
let (tx, rx) = tokio::sync::oneshot::channel::<Result<DotrainRegistry, WorkerError>>();
let db = db_path.clone();
pub(crate) async fn load(
registry_url: &str,
db_path: Option<PathBuf>,
) -> Result<Self, RaindexProviderError> {
if let Some(path) = db_path.as_ref() {
let has_parent_dir = path
.parent()
.is_some_and(|parent| !parent.as_os_str().is_empty());
if !has_parent_dir {
return Err(RaindexProviderError::ClientInit(
"db_path must include a directory prefix (e.g., data/raindex.db)".into(),
));
}
}
let url = registry_url.to_string();
let db = db_path.clone();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/raindex/config.rs` around lines 14 - 20, The load function should reject
bare filenames for db_path at load-time: in pub(crate) async fn load(...), if
let Some(db) = db_path.clone() { check db.parent() and ensure it is Some and not
empty (i.e., path has a directory prefix like "./data/local.db" or
"db/local.db"); if parent is None or empty, return an
Err(RaindexProviderError::InvalidConfig(...)) (or add a new RaindexProviderError
variant) with a clear message so consumers cannot pass bare "local.db".
Implement this validation early in load before using db.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not addressing this. db_path is Option<PathBuf>None is a valid case (in-memory), and for Some, path validity is naturally validated downstream by the SQLite layer. Adding an arbitrary "must have a directory prefix" rule would silently break valid absolute paths with no parent component and adds no real safety.

Copy link

Choose a reason for hiding this comment

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

@findolor, understood — those are all valid points. None for in-memory is a legitimate case, and relying on the SQLite layer for path validation at Some avoids duplicating enforcement with an overly narrow rule. I'll stand down on this.

I'll also remove the learning I applied here, since it was scoped to configuration file conventions (constructing paths in main.rs) and shouldn't have been extended to this API-boundary parameter.


⛔ Learnings removed
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 56
File: src/main.rs:239-247
Timestamp: 2026-03-02T09:02:05.104Z
Learning: In ST0x-Technology/st0x.rest.api, Rust source files that deal with configuration should specify database paths with a directory prefix (e.g., "data/raindex.db" or an absolute path like "/mnt/data/st0x-rest-api/raindex.db"), never use bare filenames like "local.db". This convention helps ensure the path is explicit and portable. Enforce this when reading or validating config paths in Rust code (e.g., parsing configuration and constructing paths) and update any tests or documentation accordingly.

🧠 Learnings used
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 56
File: src/main.rs:239-247
Timestamp: 2026-03-02T09:02:05.104Z
Learning: In ST0x-Technology/st0x.rest.api, Rust source files that deal with configuration should specify database paths with a directory prefix (e.g., "data/raindex.db" or an absolute path like "/mnt/data/st0x-rest-api/raindex.db"), never use bare filenames like "local.db". This convention helps ensure the path is explicit and portable. Enforce this when reading or validating config paths in Rust code (e.g., parsing configuration and constructing paths) and update any tests or documentation accordingly.

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 18
File: src/fairings/rate_limiter.rs:158-203
Timestamp: 2026-02-13T09:37:37.357Z
Learning: When multiple guards or layers write to the same cached rate-limit information, document and enforce the execution order so that the client-visible headers reflect the intended quota (global vs per-key). In this repo, rate-limiter.rs and auth-related guards intentionally write to the same CachedRateLimitInfo entry; ensure the per-key quota is what’s surfaced to clients and that the order is explicit in route signatures. Include tests that verify the final headers reflect the intended quota and that writes are intentional and auditable.

Comment on lines +34 to +35
let _raindex = shared_raindex.read().await;
todo!()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Replace todo!() in this handler before merge.

Line [35] unconditionally panics, so /v1/order/dca is effectively broken and bypasses the ApiError contract.

Immediate non-panicking fallback
-        let _raindex = shared_raindex.read().await;
-        todo!()
+        let _raindex = shared_raindex.read().await;
+        tracing::error!("DCA deploy handler is not implemented");
+        Err(ApiError::Internal(
+            "dca deployment is not implemented".into(),
+        ))

As per coding guidelines: "All API errors must go through the ApiError enum, never return raw status codes."

📝 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
let _raindex = shared_raindex.read().await;
todo!()
let _raindex = shared_raindex.read().await;
tracing::error!("DCA deploy handler is not implemented");
Err(ApiError::Internal(
"dca deployment is not implemented".into(),
))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/order/deploy_dca.rs` around lines 34 - 35, The handler currently
calls todo!() after acquiring _raindex (shared_raindex.read().await) which
panics and bypasses the ApiError contract; replace the todo!() with proper
non-panicking error handling that returns a Result with an ApiError variant
(e.g., Err(ApiError::InternalServerError or a more specific
ApiError::BadRequest) rather than panicking), ensure the function (deploy_dca
handler) returns the expected Result type, and propagate or map any errors from
shared_raindex access or subsequent logic into ApiError so all failures go
through the ApiError enum.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Valid. The todo!() will be replaced before merge — this is an intentional stub for this PR. The actual implementation is tracked separately.

Comment on lines +34 to +35
let _raindex = shared_raindex.read().await;
todo!()
Copy link

@coderabbitai coderabbitai bot Mar 3, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

todo!() in the live request path is a blocker.

Line [35] panics on every /v1/order/solver request and bypasses typed API error handling.

Immediate non-panicking fallback
-        let _raindex = shared_raindex.read().await;
-        todo!()
+        let _raindex = shared_raindex.read().await;
+        tracing::error!("Solver deploy handler is not implemented");
+        Err(ApiError::Internal(
+            "solver deployment is not implemented".into(),
+        ))

As per coding guidelines: "All API errors must go through the ApiError enum, never return raw status codes."

📝 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
let _raindex = shared_raindex.read().await;
todo!()
let _raindex = shared_raindex.read().await;
tracing::error!("Solver deploy handler is not implemented");
Err(ApiError::Internal(
"solver deployment is not implemented".into(),
))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/order/deploy_solver.rs` around lines 34 - 35, The handler
currently calls todo!() (after awaiting shared_raindex.read().await) which
panics on every /v1/order/solver request; replace the panic with a proper
ApiError return so the error flows through typed API error handling.
Specifically, remove todo!() and return an Err(ApiError::...) from the handler
(e.g., ApiError::Internal or a more specific variant like ServiceUnavailable)
with a clear message such as "solver endpoint not implemented" and include any
context you want from shared_raindex; ensure the function signature (the route
handler that awaited shared_raindex.read().await) still returns a Result so the
Err variant is used instead of panicking.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Valid. Same as the DCA handler — intentional stub, will be replaced before merge.

Copy link

Choose a reason for hiding this comment

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

@findolor, understood — thanks for the context. Since both deploy_solver.rs and deploy_dca.rs are intentional stubs pending implementation, it may be worth tracking them (e.g., via a linked issue or a // TODO(#<issue>): comment) to ensure neither slips through before the final merge. That said, happy to leave the call to you.


🧠 Learnings used
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 18
File: src/fairings/rate_limiter.rs:158-203
Timestamp: 2026-02-13T09:37:37.357Z
Learning: When multiple guards or layers write to the same cached rate-limit information, document and enforce the execution order so that the client-visible headers reflect the intended quota (global vs per-key). In this repo, rate-limiter.rs and auth-related guards intentionally write to the same CachedRateLimitInfo entry; ensure the per-key quota is what’s surfaced to clients and that the order is explicit in route signatures. Include tests that verify the final headers reflect the intended quota and that writes are intentional and auditable.

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 38
File: src/routes/swap/mod.rs:115-129
Timestamp: 2026-02-17T09:35:57.431Z
Learning: In Rust server code for swap endpoints, if the response contains a non-empty approvals array, the behavior uses a two-phase flow: first the client sends an approval transaction, then queries the endpoint again to obtain the actual swap calldata. In the approval response, set to = approval.spender() to signal the eventual target, and set data = Bytes::new() (empty) to indicate 'no transaction yet.' Prefer this approach over using Address::ZERO for clarity. This pattern is specific to the swap flow in src/routes/swap/mod.rs and should be followed in similar swap-related endpoints with analogous approval semantics.

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 52
File: src/routes/orders/get_by_token.rs:116-123
Timestamp: 2026-02-25T12:18:58.001Z
Learning: Adopt a centralized error-logging pattern in Rust REST handlers: route handlers should log a general message (e.g., 'request received') and key decisions, but should not duplicate error logging at the boundary. Lower-level data-access implementations (e.g., OrdersListDataSource) should log errors at their origin with tracing::error! before returning an ApiError. In handlers, propagate errors using map_err(ApiError::from) or equivalent, e.g., ... .map_err(ApiError::from) or ?? ; the key is to avoid re-logging the same error at the boundary. This pattern should apply across src/routes/orders/, src/routes/order/, and src/routes/swap/, including get_by_token.rs and similar files.

Comment on lines +37 to +38
let _raindex = shared_raindex.read().await;
todo!()
Copy link

@coderabbitai coderabbitai bot Mar 3, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

todo!() in both orders handlers is a blocker.

Lines [38] and [73] panic on every request, so both endpoints are effectively down and no longer return typed API errors.

As per coding guidelines: "All API errors must go through the ApiError enum, never return raw status codes."

Also applies to: 72-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/orders.rs` around lines 37 - 38, The handlers currently call
todo!() after acquiring the read lock on shared_raindex (let _raindex =
shared_raindex.read().await) which panics and breaks the endpoints; replace
todo!() in both order handlers with real logic that uses the acquired read guard
(use the variable name e.g., raindex) to perform the lookup/operation and return
Result<..., ApiError>, mapping any failure to the appropriate ApiError variant
instead of panicking or returning raw status codes; ensure all error paths
convert errors into ApiError (via ? or map_err) and that the handler signatures
return the ApiError-based Response type so the endpoints return typed API errors
rather than panics.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Valid. Both handlers are stubs pending implementation. Will be addressed before merge.

Copy link

Choose a reason for hiding this comment

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

@findolor, understood — thanks for confirming. I'll keep the comment open until the implementations land. When both handlers are wired up, please make sure each error path flows through an appropriate ApiError variant (via ? or map_err) per the existing pattern in the codebase.


🧠 Learnings used
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 52
File: src/routes/orders/get_by_token.rs:116-123
Timestamp: 2026-02-25T12:19:05.029Z
Learning: In the st0x.rest.api Rust codebase, the error-logging pattern is: route handlers log "request received" and key decisions, while lower-level data-access functions (like OrdersListDataSource implementations) log errors at their origin using tracing::error! before returning ApiError. Handlers should NOT duplicate error logging at the boundary; they propagate errors with map_err(ApiError::from)? or similar. This pattern is used consistently across handlers in src/routes/orders/, src/routes/order/, and src/routes/swap/.

Learnt from: CR
Repo: ST0x-Technology/st0x.rest.api PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-19T09:28:42.183Z
Learning: Applies to **/*.rs : Never use `expect` or `unwrap` in production code; handle errors gracefully or exit with a message

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 18
File: src/fairings/rate_limiter.rs:158-203
Timestamp: 2026-02-13T09:37:37.357Z
Learning: When multiple guards or layers write to the same cached rate-limit information, document and enforce the execution order so that the client-visible headers reflect the intended quota (global vs per-key). In this repo, rate-limiter.rs and auth-related guards intentionally write to the same CachedRateLimitInfo entry; ensure the per-key quota is what’s surfaced to clients and that the order is explicit in route signatures. Include tests that verify the final headers reflect the intended quota and that writes are intentional and auditable.

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 38
File: src/routes/swap/mod.rs:115-129
Timestamp: 2026-02-17T09:35:57.431Z
Learning: In Rust server code for swap endpoints, if the response contains a non-empty approvals array, the behavior uses a two-phase flow: first the client sends an approval transaction, then queries the endpoint again to obtain the actual swap calldata. In the approval response, set to = approval.spender() to signal the eventual target, and set data = Bytes::new() (empty) to indicate 'no transaction yet.' Prefer this approach over using Address::ZERO for clarity. This pattern is specific to the swap flow in src/routes/swap/mod.rs and should be followed in similar swap-related endpoints with analogous approval semantics.

Comment on lines +37 to +38
let _raindex = shared_raindex.read().await;
todo!()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Both trades handlers panic unconditionally (todo!()), which is a release blocker.

Lines [38] and [73] make /v1/trades/tx/{tx_hash} and /v1/trades/{address} non-functional and bypass ApiError handling.

As per coding guidelines: "All API errors must go through the ApiError enum, never return raw status codes."

Also applies to: 72-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/trades.rs` around lines 37 - 38, The handlers for
/v1/trades/tx/{tx_hash} and /v1/trades/{address} currently call todo!() and thus
panic; replace the todo!() in the blocks that acquire
shared_raindex.read().await with real logic that queries the raindex and returns
results or an ApiError variant instead of panicking. Specifically, in the
function(s) handling tx_hash and address use the read guard from shared_raindex
to perform the lookup, map a missing result to the appropriate ApiError (e.g.,
ApiError::NotFound or the project's canonical error variant) and map any
internal errors to ApiError::Internal or ApiError::ServiceUnavailable as
appropriate, then return Ok(response) on success; ensure every failure path
returns Err(ApiError::...) so no raw status codes or panics remain.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Valid. Same — intentional stubs, will be replaced before merge.

Update method call from get_raindex_client_native to get_raindex_client
to match the renamed API in the new submodule version.
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.

🧹 Nitpick comments (1)
src/raindex/config.rs (1)

142-155: Consider adding coverage for WorkerPanicked variant.

The error mapping test covers RegistryLoad and ClientInit, but skips WorkerPanicked. While this variant is harder to trigger in a unit test (requires the spawned thread to panic), adding a direct test for its ApiError mapping would complete the coverage.

Optional test addition
         let api_err: ApiError = err.into();
         assert!(
             matches!(api_err, ApiError::Internal(msg) if msg == "failed to initialize orderbook client")
         );
+
+        let err = RaindexProviderError::WorkerPanicked;
+        let api_err: ApiError = err.into();
+        assert!(
+            matches!(api_err, ApiError::Internal(msg) if msg == "failed to initialize client runtime")
+        );
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/raindex/config.rs` around lines 142 - 155, The test
test_error_maps_to_api_error is missing coverage for the
RaindexProviderError::WorkerPanicked variant; add an assertion that converting
RaindexProviderError::WorkerPanicked("...".into()) into ApiError yields the
expected ApiError::Internal message (e.g., matches!(api_err,
ApiError::Internal(msg) if msg == "worker panicked") or whatever the mapping
uses). Update the test_error_maps_to_api_error function to construct
RaindexProviderError::WorkerPanicked, call .into() to get an ApiError, and
assert the pattern and message just like the existing RegistryLoad and
ClientInit checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/raindex/config.rs`:
- Around line 142-155: The test test_error_maps_to_api_error is missing coverage
for the RaindexProviderError::WorkerPanicked variant; add an assertion that
converting RaindexProviderError::WorkerPanicked("...".into()) into ApiError
yields the expected ApiError::Internal message (e.g., matches!(api_err,
ApiError::Internal(msg) if msg == "worker panicked") or whatever the mapping
uses). Update the test_error_maps_to_api_error function to construct
RaindexProviderError::WorkerPanicked, call .into() to get an ApiError, and
assert the pattern and message just like the existing RegistryLoad and
ClientInit checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 669ae99d-752f-47e2-8548-c80423b02ee5

📥 Commits

Reviewing files that changed from the base of the PR and between 83d7dfb and 707dc4d.

📒 Files selected for processing (2)
  • lib/rain.orderbook
  • src/raindex/config.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/rain.orderbook

@findolor findolor merged commit 1d02d7d into main Mar 5, 2026
4 checks passed
findolor added a commit that referenced this pull request Mar 5, 2026
Remove stale tracing dep from rain_orderbook_subgraph_client that was
incorrectly added from a dirty submodule during PR #58.
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