Skip to content

feat(types)!: Unknown(String) fallback on every response enum - #119

Open
extremeandy wants to merge 8 commits into
masterfrom
claude/unknown-string-enum-fallback-1e103a
Open

extremeandy wants to merge 8 commits into
masterfrom
claude/unknown-string-enum-fallback-1e103a

Conversation

@extremeandy

@extremeandy extremeandy commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Response-side enums whose value set can grow now parse any value the exchange sends. A recognised string maps to its variant; anything else lands in a new Unknown(String) variant carrying the raw wire value, so callers can log what they got instead of failing the whole response.

Motivation: OrderStatus lacks TriggerFailed, which the engine emits today, so any order row or WebSocket order update carrying it fails to deserialize in 0.21.0.

What changed

  • Mechanism. serde_with's SerializeDisplay / DeserializeFromStr derives delegate serde to the strum Display / FromStr impls, so each enum's wire strings live in one place. Every open enum has a #[strum(default)] Unknown(String) variant.
  • Converted from closed to open: OrderStatus, OrderUpdateType, SystemOrderType, RfqExecutionMode, VaultRedeemStatus, TriggerBy.
  • Converted from unit Unknown to Unknown(String): Blockchain, DepositSource, DepositStatus, WithdrawalStatus, OrderBookState, RwaMarketType.
  • Kept closed and Copy because the engine's sets are fixed: Side, TimeInForce, SelfTradePrevention.
  • Backfilled variants: OrderStatus::TriggerFailed, six blockchains, the full deposit source list, and the deposit/withdrawal statuses the API reports. DepositSource::Nuvei and WithdrawalStatus::Verifying never appear on the wire and are removed.
  • RequestForQuoteUpdate is internally tagged with struct payloads, so it gets a unit #[serde(other)] Unknown instead. The raw e tag is not preserved for this one enum.
  • Request-only enums (FillType, SortDirection, OrderBookDepthLimit, VaultHistoryInterval, SlippageToleranceType) are untouched.
  • CI's clippy step now runs with --all-targets, matching just check, and the one warning that surfaced is fixed.

Breaking changes

  • Copy is removed from every converted enum (a String payload cannot be Copy). Clone is kept.
  • Unknown on the six enums that already had it now carries a String: match with Unknown(_).
  • All converted enums are #[non_exhaustive]: downstream match needs a wildcard arm.
  • DepositSource::Nuvei and WithdrawalStatus::Verifying are removed.

The only Backpack consumer is bpx-cli, pinned to 0.20. Its exhaustive matches are on TimeInForce and Side, which stay closed, so it should upgrade cleanly.

Notes

  • The API's camelCase (poem-openapi) only lowercases the first character, while strum's folds acronyms, so DepositSource::HyperEVM and DepositSource::XRP carry explicit hyperEVM / xRP overrides. Tests pin the exact strings. Verified against bpx-backend at 61e5f9a7b8.
  • Release as 0.22.0 via the Release workflow with level minor after merge.

Test plan

  • just check (fmt, clippy all targets/features)
  • just test: per-enum round-trip tests, unknown-value fallback tests, whole-message tests for triggerFailed and an unknown order event, status and RFQ event.

🤖 Generated with Claude Code

Comment thread types/src/capital.rs Outdated
Comment thread types/src/lib.rs Outdated
Comment thread types/src/order.rs Outdated
Comment thread examples/src/bin/place_orders.rs Outdated
Comment thread types/src/order.rs Outdated
Comment thread types/src/order.rs Outdated
Comment thread types/src/order.rs Outdated
extremeandy added a commit that referenced this pull request Sep 3, 2026
Replace the `String` fields on `BorrowLendMarket::state` and
`BorrowLendMarketHistoryParams::interval` with enums, following the
pattern from #119.

- `BorrowLendBookState` is a response-side enum whose set can grow, so
  it uses serde_with's `SerializeDisplay` / `DeserializeFromStr` over
  strum and carries a `#[strum(default)] Unknown(String)` fallback with
  the raw wire value.
- `BorrowLendMarketHistoryInterval` is request-only with a fixed set
  (`1d`, `1w`, `1month`, `1year`), so it stays closed and `Copy`, like
  `VaultHistoryInterval`.

Wire values verified against bpx-backend `api/types/src/borrow_lend.rs`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
karunmatharu pushed a commit that referenced this pull request Sep 14, 2026
* feat: add borrow/lend market endpoints

Expose public borrow/lend market APIs (markets, history, APY rates) with types, client methods, example, and integration tests.

* feat(types): typed enums for borrow/lend book state and history interval

Replace the `String` fields on `BorrowLendMarket::state` and
`BorrowLendMarketHistoryParams::interval` with enums, following the
pattern from #119.

- `BorrowLendBookState` is a response-side enum whose set can grow, so
  it uses serde_with's `SerializeDisplay` / `DeserializeFromStr` over
  strum and carries a `#[strum(default)] Unknown(String)` fallback with
  the raw wire value.
- `BorrowLendMarketHistoryInterval` is request-only with a fixed set
  (`1d`, `1w`, `1month`, `1year`), so it stays closed and `Copy`, like
  `VaultHistoryInterval`.

Wire values verified against bpx-backend `api/types/src/borrow_lend.rs`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
extremeandy and others added 7 commits September 14, 2026 11:51
…ponse enums

Response-side enums that were closed now parse any value the exchange sends:
a recognised string maps to its variant, anything else lands in a new
`Unknown(String)` variant carrying the raw wire value. Serde delegates to the
strum `Display` / `FromStr` impls through a small `serde_via_strum!` macro, so
each enum's wire strings live in one place.

Converted: TriggerBy, TimeInForce, SelfTradePrevention, OrderStatus,
SystemOrderType, Side, OrderUpdateType, RfqExecutionMode, VaultRedeemStatus.

BREAKING CHANGE: these enums are now `#[non_exhaustive]` (downstream `match`
needs a wildcard arm) and no longer implement `Copy` (the fallback carries a
`String`). `Clone` is kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Blockchain, DepositSource, DepositStatus, WithdrawalStatus, OrderBookState and
RwaMarketType already fell back to a unit `Unknown` variant on unrecognised
values. They now use the same strum-backed `Unknown(String)` shape as the rest
of the response enums, so callers can log the value the exchange sent.

BREAKING CHANGE: `Unknown` on these enums now carries a `String` (match with
`Unknown(_)`), the enums are `#[non_exhaustive]`, and `Copy` is dropped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nge emits

OrderStatus gains `TriggerFailed` (emitted today) and `PendingRelease`;
OrderUpdateType gains `SpeedbumpPlaced` and `SpeedbumpFailed` (wire values
`speedbumpPlaced` / `speedbumpFailed`), which arrive with the firm taker
speedbump. Message-level tests cover each, plus an unknown event and status
landing in `Unknown` with the raw value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bring Blockchain, DepositSource, DepositStatus and WithdrawalStatus up to the
set the exchange reports. DepositSource now lists every chain plus the payment
processors and internal transfers; its multi-word names carry explicit wire
strings (`hyperEVM`, `xRP`, `0G`) because the API only lowercases the first
character. `DepositSource::Nuvei` and `WithdrawalStatus::Verifying` never
appear on the wire and are deprecated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RequestForQuoteUpdate is internally tagged on `e` with struct payloads, so it
cannot use the strum-backed fallback. It gains a unit `#[serde(other)] Unknown`
variant and is `#[non_exhaustive]`, so an event type this client does not know
parses instead of failing the stream. The raw tag is not preserved for this one
enum.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t finds

CI ran clippy without `--all-targets`, so integration tests were never
linted and `just check` disagreed with CI. Align the two and drop the
needless borrow in `get_historical_fills.rs` that the wider run flags.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Use serde_with's `SerializeDisplay` / `DeserializeFromStr` derives instead
  of a hand-rolled macro.
- Keep `Side`, `TimeInForce` and `SelfTradePrevention` closed and `Copy`: the
  engine's sets are fixed. Revert the example clone that came with it.
- Delete `DepositSource::Nuvei` and `WithdrawalStatus::Verifying` rather than
  deprecating them; neither appears on the wire.
- Defer `PendingRelease`, `SpeedbumpPlaced` and `SpeedbumpFailed` to a stacked
  PR gated on the backend speedbump change. `TriggerFailed` stays: it is
  emitted today.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@extremeandy
extremeandy force-pushed the claude/unknown-string-enum-fallback-1e103a branch from e45c5bc to f5d723a Compare September 14, 2026 01:51
bpx-backend master added both payment processors since the audit this branch
was built against.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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