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

  1. Audit values: `SELECT DISTINCT trigger_type FROM orders` and fix anything outside the valid set plus 'NO_TRIGGER'
  2. Make the writer persist canonical TriggerType strings (or the NO_TRIGGER sentinel)
  3. Trim/uppercase the column via migration if only formatting differs
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/566d5851a90554ae. Report an issue: GitHub.