{"record":{"id":"a7a39b7f4c9be2e5","repo":"nautechsystems/nautilus_trader","slug":"bookorder-side-must-be-buy-or-sell-a7a39b","errorCode":null,"errorMessage":"BookOrder side must be Buy or Sell","messagePattern":"BookOrder side must be Buy or Sell","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/model/src/orderbook/book.rs","lineNumber":130,"sourceCode":"    }\n\n    /// Resets the order book to its initial empty state.\n    pub fn reset(&mut self) {\n        self.bids.clear();\n        self.asks.clear();\n        self.sequence = 0;\n        self.ts_last = UnixNanos::default();\n        self.update_count = 0;\n    }\n\n    /// Adds an order to the book after preprocessing based on book type.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `order.side` is `None`.\n    pub fn add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {\n        let order = pre_process_order(self.book_type, order, flags);\n        match order.side.expect(\"BookOrder side must be Buy or Sell\") {\n            OrderSide::Buy => self.bids.add(order, flags),\n            OrderSide::Sell => self.asks.add(order, flags),\n        }\n\n        self.increment(sequence, ts_event, flags);\n    }\n\n    /// Updates an existing order in the book after preprocessing based on book type.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `order.side` is `None`.\n    pub fn update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {\n        let order = pre_process_order(self.book_type, order, flags);\n        match order.side.expect(\"BookOrder side must be Buy or Sell\") {\n            OrderSide::Buy => self.bids.update(order, flags),\n            OrderSide::Sell => self.asks.update(order, flags),\n        }","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/orderbook/book.rs#L112-L148","documentation":"OrderBook::add unwraps the order's `side` field with expect and panics with this message when it is None. BookOrder models side as an Option<OrderSide> so it can round-trip feed data where the side is missing or unparseable; the book ladder (bids/asks) cannot route an order without a side, so the library treats a None side as a hard programming/data error and panics instead of silently dropping the update.","triggerScenarios":"Calling OrderBook::add (directly, from Python via py_add, or indirectly through apply_delta / snapshot parsing adapters like parse_book_snapshot_response, parse_order_book, parse_l2_book_snapshot) with a BookOrder whose `side` is None — typically constructed from feed data where the side field was absent or did not map to Buy/Sell.","commonSituations":"Custom adapter code building BookOrder from a vendor feed that emits unknown/blank side values; a new venue whose side mapping isn't handled in the parser; L1/L2/L3 preprocessing (pre_process_order) does not fill in a missing side, so any None survives to the expect.","solutions":["Inspect the BookOrder being passed to add and ensure `side` is Some(OrderSide::Buy) or Some(OrderSide::Sell) before constructing it.","In the feed adapter/parser, map every raw side value explicitly to Buy/Sell and skip (with a warning) records that have no valid side instead of building a BookOrder with side None.","Log the offending raw record (price, qty, sequence) at the parser level before constructing BookOrder so the source of the None side can be identified.","If side can legitimately be missing for your book type, use an L1/L2 aggregation path that tolerates it, or guard the call site with `if let Some(side) = order.side`.","Upgrade to the latest nautilus_model version — check release notes in case side handling for your venue adapter changed."],"exampleFix":"// before\nlet order = BookOrder::new(side_from_feed, price, size, order_id); // side_from_feed: Option<OrderSide>\nbook.add(order, flags, sequence, ts_event);\n// after\nmatch side_from_feed {\n    Some(side @ (OrderSide::Buy | OrderSide::Sell)) => {\n        book.add(BookOrder::new(side, price, size, order_id), flags, sequence, ts_event);\n    }\n    None => log::warn!(\"skipping depth record with missing side, seq={sequence}\"),\n}","handlingStrategy":"validation","validationCode":"if order.side.is_none() {\n    log::warn!(\"skip book add: missing side for id={:?}\", order.order_id);\n    return;\n}\nbook.add(order, flags, sequence, ts_event);","typeGuard":"fn has_side(order: &BookOrder) -> bool {\n    matches!(order.side, Some(OrderSide::Buy | OrderSide::Sell))\n}","tryCatchPattern":"// Rust panics are not catchable here; validate before calling. In Python bindings, wrap in try/except BaseException only for research tooling:\ntry:\n    book.add(order, flags, sequence, ts_event)\nexcept BaseException as e:\n    log.warning(\"book add panicked: %s\", e)","preventionTips":["Never construct BookOrder with side None; make the feed->BookOrder mapping total (all enum values handled).","Skip-and-log invalid feed records at the parser boundary.","Add unit tests for adapter side mapping including unknown values.","Assert side presence where deltas are decoded, not deep inside book code."],"tags":["rust","orderbook","panic","null-argument"],"backgroundTag":"null-argument","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}