nautechsystems/nautilus_trader · critical
Invalid `LiquiditySide`
Error message
Invalid `LiquiditySide`
What it means
While hydrating the optional `liquidity_side` column, `LiquiditySide::from_str` result is unwrapped with expect(). The panic means a non-NULL liquidity_side value in the row (expected 'MAKER'/'TAKER'/'NONE') is not a valid LiquiditySide, so the order row fails to load.
Source
Thrown at crates/infrastructure/src/sql/models/orders.rs:1103
.ok()
.and_then(|x| x.and_then(|s| Decimal::from_str(s).ok()));
let trailing_offset_type = row
.try_get::<Option<TrailingOffsetTypePg>, _>("trailing_offset_type")
.ok()
.flatten()
.and_then(|value| value.0);
let time_in_force = row
.try_get::<&str, _>("time_in_force")
.map(|x| TimeInForce::from_str(x).expect("Invalid `TimeInForce`"))?;
let expire_time = row
.try_get::<Option<&str>, _>("expire_time")
.ok()
.and_then(|x| x.map(UnixNanos::from));
let filled_qty = row.try_get::<&str, _>("filled_qty").map(Quantity::from)?;
let liquidity_side = row
.try_get::<Option<&str>, _>("liquidity_side")
.ok()
.and_then(|x| x.map(|x| LiquiditySide::from_str(x).expect("Invalid `LiquiditySide`")));
let avg_px = row.try_get::<Option<Decimal>, _>("avg_px").ok().flatten();
let slippage = row.try_get::<Option<Decimal>, _>("slippage").ok().flatten();
let commissions = row
.try_get::<Option<Vec<String>>, _>("commissions")?
.map_or_else(Vec::new, |c| {
c.into_iter().map(|s| Money::from(&s)).collect()
});
let status = row
.try_get::<&str, _>("status")
.map(|x| OrderStatus::from_str(x).expect("Invalid `OrderStatus`"))?;
let is_post_only = row.try_get::<bool, _>("is_post_only")?;
let is_reduce_only = row.try_get::<bool, _>("is_reduce_only")?;
let is_quote_quantity = row.try_get::<bool, _>("is_quote_quantity")?;
let display_qty = row
.try_get::<Option<&str>, _>("display_qty")
.ok()
.and_then(|x| x.map(Quantity::from));
let emulation_trigger = rowView on GitHub (pinned to 18893faf8b)
Solutions
- Audit the column: `SELECT DISTINCT liquidity_side FROM orders WHERE liquidity_side IS NOT NULL` and fix offending values
- Persist canonical LiquiditySide strings from the writer path
- Normalize values (trim/upper) in a migration if that is the only mismatch
- Map the parse failure into an error instead of expect when reading external data
Example fix
// before
x.map(|x| LiquiditySide::from_str(x).expect("Invalid `LiquiditySide`"))
// after
x.map(|x| LiquiditySide::from_str(x).map_err(|e| ModelError::Parse(format!("invalid liquidity_side '{x}': {e}")))).transpose()? Defensive patterns
Strategy: type-guard
Validate before calling
fn valid_liquidity_side(s: Option<&str>) -> bool {
s.map(|s| LiquiditySide::from_str(s).is_ok()).unwrap_or(true)
} Type guard
fn is_known_liquidity_side(s: &str) -> bool {
LiquiditySide::from_str(s).is_ok()
} Try / catch
let liquidity_side = x
.map(|x| LiquiditySide::from_str(x)
.map_err(|e| ModelError::Parse(format!("row {}: invalid liquidity_side '{x}': {e}", row_id))))
.transpose()?; Prevention
- Write None rather than 'UNKNOWN' for absent liquidity sides
- Normalize case before persisting
- Add a CHECK constraint on liquidity_side
- Validate exchange fill reports map to MAKER/TAKER before insert
When it happens
Trigger: Calling `OrderModel::from_row` on a row whose `liquidity_side` contains an unrecognized string (wrong case like 'maker' if the parser is case-sensitive, abbreviations 'M'/'T', or values from another system).
Common situations: Data imported from exchange reports or third-party databases using different liquidity naming; mixed-version writers; manual updates.
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 `OrderType`
- Invalid `OrderSide`
- Invalid `TimeInForce`
- Invalid `OrderStatus`
- Invalid `TriggerType`
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/24cb415dcab8ef30.
Report an issue: GitHub.