nautechsystems/nautilus_trader · critical
Invalid `TriggerType`
Error message
Invalid `TriggerType`
What it means
`parse_trigger_type` converts the optional `trigger_type` column into an `Option<TriggerType>`: the sentinel string 'NO_TRIGGER' (case-insensitive) maps to None, anything else is parsed with `TriggerType::from_str` and unwrapped with expect(). The panic means the stored trigger type string is neither the sentinel nor a valid TriggerType variant (expected e.g. 'DEFAULT','BID','ASK','LAST',...).
Source
Thrown at crates/infrastructure/src/sql/models/orders.rs:1257
decoded
.into_iter()
.map(|(k, v)| (Ustr::from(k.as_str()), Ustr::from(v.as_str())))
.collect(),
))
}
fn tags_from_row(row: &PgRow) -> Option<Vec<Ustr>> {
row.try_get::<Vec<String>, _>("tags")
.ok()
.map(|tags| tags.iter().map(|tag| Ustr::from(tag.as_str())).collect())
}
fn parse_trigger_type(value: Option<&str>) -> Option<TriggerType> {
value.and_then(|value| {
if value.eq_ignore_ascii_case("NO_TRIGGER") {
None
} else {
Some(TriggerType::from_str(value).expect("Invalid `TriggerType`"))
}
})
}
fn parse_contingency_type(value: Option<&str>) -> Option<ContingencyType> {
value.and_then(|value| {
if value.eq_ignore_ascii_case("NO_CONTINGENCY") {
None
} else {
Some(ContingencyType::from_str(value).expect("Invalid `ContingencyType`"))
}
})
}
#[cfg(test)]
mod tests {
use rstest::rstest;
View on GitHub (pinned to 18893faf8b)
Solutions
- Audit values: `SELECT DISTINCT trigger_type FROM orders` and fix anything outside the valid set plus 'NO_TRIGGER'
- Make the writer persist canonical TriggerType strings (or the NO_TRIGGER sentinel)
- Trim/uppercase the column via migration if only formatting differs
- Return an error instead of expect when deserializing untrusted rows
Example fix
// before
Some(TriggerType::from_str(value).expect("Invalid `TriggerType`"))
// after
TriggerType::from_str(value.trim())
.map(Some)
.map_err(|e| ModelError::Parse(format!("invalid trigger_type '{value}': {e}")))? Defensive patterns
Strategy: type-guard
Validate before calling
fn valid_trigger_type(s: Option<&str>) -> bool {
s.map(|s| s.eq_ignore_ascii_case("NO_TRIGGER") || TriggerType::from_str(s).is_ok()).unwrap_or(true)
} Type guard
fn is_known_trigger_type(s: &str) -> bool {
s.eq_ignore_ascii_case("NO_TRIGGER") || TriggerType::from_str(s).is_ok()
} Try / catch
TriggerType::from_str(value.trim())
.map(Some)
.map_err(|e| ModelError::Parse(format!("row: invalid trigger_type '{value}': {e}")))? Prevention
- Use the 'NO_TRIGGER' sentinel exactly as defined for absent triggers
- Persist TriggerType via canonical serialization only
- Normalize (trim) values at write time
- Audit trigger_type values after adapter or nautilus version changes
When it happens
Trigger: Calling `OrderModel::from_row` (via parse_trigger_type) where `trigger_type` holds an unrecognized value: 'DEFAULT ' with whitespace, lowercase variants if the parser is case-sensitive, adapter-specific names like 'MARK', or NULL-handling gone wrong upstream.
Common situations: Rows written by other systems or older nautilus versions with different trigger naming; manual inserts; adapters mapping exchange trigger kinds to non-canonical strings.
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 `LiquiditySide`
- Invalid `OrderStatus`
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/566d5851a90554ae.
Report an issue: GitHub.