Skip to content

orders: #825 review follow-ups - #829

Merged
wboayue merged 1 commit into
mainfrom
orders-wire-enums-followups
Sep 21, 2026
Merged

wboayue merged 1 commit into
mainfrom
orders-wire-enums-followups

Conversation

@wboayue

@wboayue wboayue commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Addresses the review on #825. Six findings land here; two are deferred to issues. Plan and reasoning: plans/orders-wire-enum-followups.md.

1. The required-action invariant leaked one layer up

#825 made a present-but-empty action Error::Parse. But decode_open_order_proto / decode_completed_order_proto / decode_execution_data_proto did

let order = p.order.as_ref().map(decode_order).transpose()?.unwrap_or_default();

so a wholly absent order submessage still produced Order::default()action == Buy, exactly the mishandling §14 says it removed. Present-but-empty errored; absent defaulted.

The reference client does neither: EDecoder.cs's OpenOrderEventProtoBuf (:2652) and ExecutionDataEventProtoBuf (:2918) drop the frame, returning before eWrapper.openOrder(..) / execDetails(..) if a submessage is null. They never synthesize a default. This crate has no skip channel, so a missing submessage is now Error::Parse. Defaulting is the one option neither client takes — it hands the caller a phantom BUY order over an empty contract, which reads as real data.

decode_order_bound's local required helper folds into the shared one.

2. #[repr(i32)] and the discriminants were decorative

Once From<T> for i32 is hand-written, = 0 / = 1 drive nothing — and variant as i32 does not compile either way, once a payload variant exists. They were the only reason #[repr(i32)] was needed (explicit discriminants on a data-carrying enum require a primitive repr), so it was an ABI commitment in the public API buying nothing.

Worse, they were a second source of truth. A missing match arm is a compile error; Foo = 5 paired with Foo => 6 is not.

Dropped from all eight, Liquidity included — it has no From<Liquidity> for i32 at all, so its discriminants were never read either, and leaving it out would have split enum-typing.md's shape description in two. Each variant's wire code is now rustdoc (Wire code `0`.), which is strictly more visible than a discriminant.

3. *_round_trips_every_wire_code did not check "every"

Seven tests so named verified only the rows listed; adding OcaType::Reserved = 7 broke none of them. #822 hit this and answered it with all_tifs_covers_every_variant.

Integer enums admit a cheaper guard than a per-enum modeled_index match: From<i32> is total, so every code outside the table must be Unknown(code). check_wire_code_round_trip now takes the Unknown constructor and probes -8..=64, skipping listed codes. Verified by temporarily adding a Reserved = 7 variant:

assertion `left == right` failed: code 7 is modeled by a variant the table does not list

5. enum-typing.md's AuctionStrategy claim was false

The node said From<i32> for AuctionStrategy "converts nothing but the i32 a caller hands OrderBuilder". OrderBuilder::auction_strategy was a private field with no setter — the only writes were None at construction, so the .into() reading it was unreachable. Field deleted, claim corrected. The missing typed setter is the builder-enum-coverage gap the dead field was hiding: issue #828.

6. "All eight macros in src/" is nine

grep -rn "macro_rules!" src --include=*.rs | wc -l → 9. impl_proto_payload! had no row. #825 re-derived the impl_wire_enum! count on the line below but left the total.

7. impl_wire_enum! empty-input message asymmetry

The closed arm reported "unknown Action" for empty input where the fallback arm reported "empty $name". Both now distinguish no-value from unrecognized-value. Only observable through a direct "".parse()parse_required intercepts empty first with "missing {label}", which names the field rather than the type.


Deferred

Gate

cargo fmt --check; clippy × 3 configs; just test (6 legs, 4393 tests, 0 failed); RUSTDOCFLAGS="-D warnings" cargo doc × 3; cargo build --examples × 2; cargo build -p ibapi-integration-{sync,async} --tests; just rules-check — all clean.

Six findings from the #825 review; two deferred to issues.

Required submessages. #825 made a present-but-empty `action` Error::Parse,
but decode_open_order_proto / decode_completed_order_proto /
decode_execution_data_proto still defaulted a wholly absent `order` to
Order::default() - action == Buy, the mishandling it removed. Upstream
drops such a frame (EDecoder.cs returns before eWrapper.openOrder); this
crate has no skip channel, so it errors. Folds decode_order_bound's local
`required` into the shared one.

repr(i32) and discriminants dropped from all eight integer-coded enums,
Liquidity included. Nothing reads them once a payload variant exists -
`variant as i32` does not compile either way - so `= 0` duplicated the
hand-written From<T> for i32, and repr(i32) was an ABI commitment buying
nothing. Wire codes move to each variant's rustdoc.

check_wire_code_round_trip takes the Unknown constructor and probes
-8..=64 for codes the table omits, so the callers' _every_wire_code names
are true: a variant added without a table row now fails.

Docs: enum-typing's AuctionStrategy note named an OrderBuilder field as
the conversion's one caller, but the field had no setter and the line
reading it was unreachable - field deleted, claim corrected, setter gap
filed as #828. macros-last-resort said eight macros where grep finds
nine; impl_proto_payload! gains a row.

impl_wire_enum!'s closed arm reported "unknown $name" for empty input
where the fallback arm reported "empty $name"; both now distinguish the
two.

Deferred: OrderCondition's discriminator fallback and panic (#827), which
#825 called a separate change, and Unknown(code) aliasing a known code
under derived PartialEq, inherited from the Liquidity precedent.
@wboayue
wboayue merged commit 1d07263 into main Sep 21, 2026
4 checks passed
@wboayue
wboayue deleted the orders-wire-enums-followups branch September 21, 2026 00:43
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