Skip to content

Add ok/err type for oracle response#6

Open
rouzwelt wants to merge 5 commits into
ST0x-Technology:mainfrom
rouzwelt:2026-04-14-ok-err-vec-response
Open

Add ok/err type for oracle response#6
rouzwelt wants to merge 5 commits into
ST0x-Technology:mainfrom
rouzwelt:2026-04-14-ok-err-vec-response

Conversation

@rouzwelt

@rouzwelt rouzwelt commented Apr 14, 2026

Copy link
Copy Markdown

Motivation

resolves #5

Solution

Checks

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

  • added comprehensive test coverage for any changes in logic
  • made this PR as small as possible
  • linked any relevant issues or PRs
  • included screenshots (if this involves a change to a front-end/dashboard)

Summary by CodeRabbit

  • New Features

    • Improved batch handling so individual request errors don't fail the whole batch.
    • Per-request error details are returned inside the response payload with explicit status (Ok/Err).
  • Tests

    • Integration and smoke tests updated; single-request unknown-token now yields HTTP 200 with an Err response.
    • Added mixed-batch test covering combined success + error responses.

@rouzwelt rouzwelt changed the title init Add ok/err type for oracle response Apr 14, 2026
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@rouzwelt has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 0 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 0 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6341340-8fbb-44d9-bd36-e4b090365a9f

📥 Commits

Reviewing files that changed from the base of the PR and between 28baaea and 6eb8b0b.

📒 Files selected for processing (4)
  • src/lib.rs
  • src/oracle.rs
  • tests/integration.rs
  • tests/smoke_prod.rs
📝 Walkthrough

Walkthrough

Batch request handling now accumulates per-request results instead of failing fast. Responses use a tagged OracleResponse enum with Ok(OracleOkResponse) and Err(OracleErrResponse). Resolution and response-building functions were changed to return per-request error wrappers; tests updated to assert the new shape.

Changes

Cohort / File(s) Summary
Oracle response shape
src/oracle.rs
Replaced single OracleResponse struct with OracleOkResponse and OracleErrResponse, added OracleResponse enum (#[serde(tag="status")]), From<AppError> for OracleErrResponse, and helper methods into_result() / as_result().
Request handling & error flow
src/lib.rs
Changed pair resolution to return Result<ResolvedPair, OracleErrResponse> and accumulate per-request results; build_response_from_quote returns OracleResponse directly; missing cache or resolution failures map to OracleResponse::Err(...); AppError expanded with thiserror::Error.
Tests updated for new shape
tests/integration.rs, tests/smoke_prod.rs
Adjusted tests to expect HTTP 200 for partially failing requests; added mixed-batch test; updated assertions to use responses[i].as_result().unwrap() before accessing context and to verify Err variants for unknown tokens.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as Request Handler
    participant Resolver as Pair Resolver
    participant Builder as Response Builder

    Client->>Handler: Batch request (Vec<orders>)
    Note over Handler: For each order in batch
    Handler->>Resolver: resolve_pair_for_order(order)
    alt resolved
        Resolver-->>Handler: Ok(ResolvedPair)
        Handler->>Builder: build_response_from_quote(ResolvedPair)
        alt built ok
            Builder-->>Handler: OracleResponse::Ok(OracleOkResponse)
        else built err
            Builder-->>Handler: OracleResponse::Err(OracleErrResponse)
        end
    else resolution failed
        Resolver-->>Handler: Err(OracleErrResponse)
        Handler-->>Handler: OracleResponse::Err(OracleErrResponse)
    end
    Note over Handler: Accumulate Vec<OracleResponse> (mix Ok/Err)
    Handler->>Client: HTTP 200 with Vec<OracleResponse>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through code with whiskers bright and keen,

Each order now gets its own Ok or Err scene.
No more whole-batch tumble when one token hides,
Responses line up neatly with errors by their sides.
Hop, the server sings — partial wins are seen.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: introducing ok/err variants for oracle response items, which is the core objective of the PR.
Linked Issues check ✅ Passed The PR successfully implements all requirements from issue #5: per-item ok/err variants, per-request error handling, and overall success when at least one request succeeds.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing per-item ok/err response types and error handling, with no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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 unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
src/lib.rs (1)

145-148: Use .map() instead of .and_then(|p| Some(...)).

The and_then closure that wraps in Some is equivalent to map.

♻️ Proposed fix
     let needed_symbols: Vec<&str> = resolved
         .iter()
-        .filter_map(|(_, res)| res.as_ref().ok().and_then(|p| Some(p.symbol.as_str())))
+        .filter_map(|(_, res)| res.as_ref().ok().map(|p| p.symbol.as_str()))
         .collect();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib.rs` around lines 145 - 148, The closure in the computation of
needed_symbols uses and_then(|p| Some(p.symbol.as_str())), which is equivalent
to map; update the iterator chain that builds needed_symbols (the
resolved.iter().filter_map(...) expression) to replace and_then(|p|
Some(p.symbol.as_str())) with map(|p| p.symbol.as_str()) so the intent is
clearer and removes the unnecessary Some wrapper.
tests/smoke_prod.rs (1)

123-146: Consider extracting unwrapped results to reduce repetition.

The same as_result().unwrap() call is made multiple times on the same response objects. While acceptable in test code, extracting to local variables would improve readability.

♻️ Optional refactor
     assert_eq!(responses.len(), 2, "batch → length-2 array");
-    assert_eq!(responses[0].as_result().unwrap().context.len(), 3);
-    assert_eq!(responses[1].as_result().unwrap().context.len(), 3);
+    let r0 = responses[0].as_result().unwrap();
+    let r1 = responses[1].as_result().unwrap();
+    assert_eq!(r0.context.len(), 3);
+    assert_eq!(r1.context.len(), 3);

     // The two responses should carry the SAME publish_time because both
     // resolve to COIN and read the same cache entry.
     assert_eq!(
-        responses[0].as_result().unwrap().context[2],
-        responses[1].as_result().unwrap().context[2],
+        r0.context[2],
+        r1.context[2],
         "both orders resolve to COIN and should share publish_time"
     );

     // And the second should be the inverse (1/bid) of the first side.
-    let buy = Float::from(alloy::primitives::B256::from(
-        responses[0].as_result().unwrap().context[1],
-    ))
-    .format()
-    .unwrap();
-    let sell = Float::from(alloy::primitives::B256::from(
-        responses[1].as_result().unwrap().context[1],
-    ))
-    .format()
-    .unwrap();
+    let buy = Float::from(alloy::primitives::B256::from(r0.context[1]))
+        .format()
+        .unwrap();
+    let sell = Float::from(alloy::primitives::B256::from(r1.context[1]))
+        .format()
+        .unwrap();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/smoke_prod.rs` around lines 123 - 146, Extract the repeated
responses[0].as_result().unwrap() and responses[1].as_result().unwrap() calls
into local variables (e.g., let r0 = responses[0].as_result().unwrap(); let r1 =
responses[1].as_result().unwrap()) and then use r0 and r1 for all subsequent
.context accesses, length assertions, publish_time comparison, and when building
buy/sell (with Float::from(alloy::primitives::B256::from(...))). This reduces
duplication while keeping the same assertions and messages (including the
publish_time equality check and buy/sell non-equality).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/integration.rs`:
- Around line 195-200: The test currently discards the boolean returned by
matches!(responses[0], OracleResponse::Err(_)), so it never fails; change it to
assert the result by wrapping the matches! call in an assert! (e.g.,
assert!(matches!(responses[0], OracleResponse::Err(_)))); this ensures the test
actually fails when responses[0] is not an OracleResponse::Err variant. Use the
existing responses variable and the OracleResponse enum in the assertion.

---

Nitpick comments:
In `@src/lib.rs`:
- Around line 145-148: The closure in the computation of needed_symbols uses
and_then(|p| Some(p.symbol.as_str())), which is equivalent to map; update the
iterator chain that builds needed_symbols (the resolved.iter().filter_map(...)
expression) to replace and_then(|p| Some(p.symbol.as_str())) with map(|p|
p.symbol.as_str()) so the intent is clearer and removes the unnecessary Some
wrapper.

In `@tests/smoke_prod.rs`:
- Around line 123-146: Extract the repeated responses[0].as_result().unwrap()
and responses[1].as_result().unwrap() calls into local variables (e.g., let r0 =
responses[0].as_result().unwrap(); let r1 = responses[1].as_result().unwrap())
and then use r0 and r1 for all subsequent .context accesses, length assertions,
publish_time comparison, and when building buy/sell (with
Float::from(alloy::primitives::B256::from(...))). This reduces duplication while
keeping the same assertions and messages (including the publish_time equality
check and buy/sell non-equality).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5bc65d78-069c-4e96-8541-b27f9949e85f

📥 Commits

Reviewing files that changed from the base of the PR and between 2af911e and 45e8798.

📒 Files selected for processing (4)
  • src/lib.rs
  • src/oracle.rs
  • tests/integration.rs
  • tests/smoke_prod.rs

Comment thread tests/integration.rs Outdated

@hardyjosh hardyjosh 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.

Architectural triage — not a correctness review. A few things worth resolving before merge, mostly around naming, leaky internal types, and the silent broadening of the wire-format change beyond what the PR description advertises. CodeRabbit's nits already stand; the inline comments below are the non-overlapping ones.

Comment thread src/oracle.rs
/// Oracle response matching Rain's SignedContextV1 format.
/// The JSON array shape of this struct is what upstream
/// `rain.orderbook/crates/quote/src/oracle.rs` expects to deserialize.
/// Oracle ok response matching Rain's SignedContextV2 format.

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.

Doc says SignedContextV2 but SCHEMA_VERSION (line 10, unchanged) is still 1. The wire format is changing (single struct → {status, ...} envelope) and the comment at the const explicitly says to bump this whenever the layout changes. Either bump to 2 so strategies can reject old-shape clients, or leave a note stating the deliberate decision not to. Worth calling out in the PR description either way.

Comment thread src/oracle.rs

/// Single oracle result, can be ok or err.
/// The JSON array shape of this struct is what upstream
/// `rain.orderbook/crates/quote/src/oracle.rs` expects to deserialize.

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.

This comment (and the copy of it at line 63 on the OracleResponse alias) claims rain.orderbook/crates/quote/src/oracle.rs expects to deserialize this shape. That file doesn't exist in rain.orderbook — grepped crates/quote/ and crates/common/. The reference was already stale pre-PR; now it's duplicated. Either point at the real consumer or drop the reference so future readers don't chase a ghost.

Comment thread src/oracle.rs
/// List of oracle result.
/// The JSON shape of this struct is what upstream
/// `rain.orderbook/crates/quote/src/oracle.rs` expects to deserialize.
pub type OracleResponse = Vec<OracleResult>;

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.

Naming foot-gun: OracleResponse used to be the single-response struct. Keeping the identifier and making it a Vec<OracleResult> alias means OracleResponse::new() now silently means Vec::new() (see lib.rs:133). Consider OracleBatchResponse (or OracleResults) so the old name doesn't silently change meaning.

Comment thread src/lib.rs
None => OracleResult::Err(AppError::Unavailable(format!(
"No cached quote for {} yet. The poll loop has not succeeded since startup.",
pair.symbol
)).into())

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.

This quietly broadens the PR's scope: AppError::Unavailable (transient — cache hasn't populated yet) now collapses into 200 OK + inner Err, same as BadRequest (permanent). That erases the transport-layer distinction between "retry might work" and "your request is malformed" — clients can't use HTTP retry heuristics anymore. The PR description only mentions ok/err framing; this extra behavioural change isn't called out. Intentional? If yes, worth adding to the description and probably encoding transient-ness in the error payload so clients can still tell them apart.

Comment thread src/lib.rs
input_io_index: alloy::primitives::U256,
output_io_index: alloy::primitives::U256,
) -> Result<ResolvedPair, AppError> {
) -> Result<ResolvedPair, OracleErrResponse> {

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.

Internal helper now returns the wire type OracleErrResponse. That leaks — an internal resolver should fail with AppError and be converted to the wire envelope at the HTTP boundary (in post_signed_context_v1). Keeping AppError here keeps wire-shape construction in one place and lets any future caller reuse the resolver.

Comment thread src/lib.rs
pair: &ResolvedPair,
quote: &crate::alpaca::QuoteData,
) -> Result<oracle::OracleResponse, AppError> {
) -> OracleResult {

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.

Return type changing from Result<_, AppError> to OracleResult kills ? propagation — the body below is now nested match + map_or_else. Cleaner: keep Result<OracleOkResponse, OracleErrResponse> (or Result<_, AppError>) internally so ? still works, and convert once at the caller when pushing onto the Vec. Reads better and removes the map_or_else around sign_context.

Comment thread src/lib.rs
Err(e) => OracleResult::Err(OracleErrResponse { msg: e.to_string() }),
Ok(context) => state.signer.sign_context(&context).await.map_or_else(
|err| {
OracleResult::Err(OracleErrResponse {

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.

Inconsistent construction: a few lines up (242–247 and 254–256) you go through AppError::X(...).into() via the From<AppError> impl. Here and at 271 you build OracleErrResponse { msg: ... } directly. Pick one — either always go through AppError (so formatting stays centralised in the Display impl) or always construct OracleErrResponse directly (and drop the From impl).

Comment thread tests/integration.rs
#[tokio::test]
async fn test_v1_batch_returns_mixed_result() {
let app = test_app().await;
// Two orders: one i sknown and chade, other is unknown and not cached.

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.

Typo: one i sknown and chade, other is unknown and not cachedone is known and cached, the other is unknown and not cached.

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.

Add ok/err types to items of the response list

2 participants