nautechsystems/nautilus_trader · error
Order side must be Buy or Sell
Error message
Order side must be Buy or Sell
What it means
The FFI constructor `BookPrice` (in crates/model/src/ffi/orderbook/level.rs) panics when the `order_side` argument cannot be decoded into a Buy/Sell side via `as_option()`. A BookLevel must be tagged with a valid side to know whether to aggregate bid or ask orders, so an invalid side aborts construction. This guards the C ABI against invalid enum input.
Source
Thrown at crates/model/src/ffi/orderbook/level.rs:50
///
/// Panics if `order_side` is `NoOrderSide`.
///
/// Returns an owning pointer to the heap-allocated `BookLevel` which the caller must
/// eventually pass to [`level_drop`].
pub unsafe extern "C" fn level_new(
order_side: OrderSideOptional,
price: Price,
orders: CVec,
) -> *mut BookLevel {
let orders = unsafe { orders.into_vec::<BookOrderFfi>() }
.into_iter()
.map(Into::into)
.collect::<Vec<BookOrder>>();
let price = BookPrice {
value: price,
side: order_side
.as_option()
.expect("Order side must be Buy or Sell"),
};
let mut level = BookLevel::new(price);
level.add_bulk(&orders);
Box::into_raw(Box::new(level))
}
/// # Safety
///
/// `level` must be a live owning pointer returned by [`level_new`] or [`level_clone`],
/// and must not be used after this call.
///
/// # Panics
///
/// Panics if `level` is null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn level_drop(level: *mut BookLevel) {
abort_on_panic(|| {
assert!(!level.is_null(), "`level` was NULL");View on GitHub (pinned to 18893faf8b)
Solutions
- Pass a concrete OrderSide (BUY or SELL) when constructing the level.
- Validate the side value in the caller language before crossing the FFI boundary.
- Inspect the call site for a variable that was never assigned a side (defaulted to None).
- Rebuild generated bindings if the enum discriminants changed between versions.
Example fix
// before level = BookPrice(price, None, orders) // after side = OrderSide.BUY # or OrderSide.SELL level = BookPrice(price, side, orders)
Defensive patterns
Strategy: validation
Validate before calling
# Python caller
assert side in (OrderSide.BUY, OrderSide.SELL), f"invalid side: {side}"
price_ptr = BookPrice(price, side, orders) Type guard
def is_valid_side(side) -> bool:
return side in (OrderSide.BUY, OrderSide.SELL) Prevention
- Default side explicitly (BUY/SELL) rather than leaving it None.
- Validate enum values at the binding layer before FFI calls.
- Avoid zero-initialized FFI structs where 0 is not a valid discriminant.
When it happens
Trigger: Calling the exported `BookPrice` FFI function with a null/invalid order_side handle so `OrderSide::as_option()` returns None while building the BookPrice struct.
Common situations: Bindings passing Python None for side, integer side values outside 1..2, or uninitialized FFI struct memory when constructing a book level from raw orders.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid scientific notation exponent '{exponent}': must be a
- invalid `AggressorSide` enum string value, was '{value}'
- invalid `AssetClass` enum string value, was '{value}'
- invalid `InstrumentClass` enum string value, was '{value}'
- invalid `BarAggregation` enum string value, was '{value}'
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/72a828ba0bba3319.
Report an issue: GitHub.