nautechsystems/nautilus_trader · error · anyhow::Error

Invalid bar spec: empty string

Error message

Invalid bar spec: empty string

What it means

Tardis parse_bar_spec splits a bar spec string on '_' and reads the last part for the numeric step plus aggregation suffix. If parts.last() returns None — which for str::split can only happen via an empty input being mishandled downstream — the function rejects the spec as empty. It is the first guard against malformed bar specification strings.

Source

Thrown at crates/adapters/tardis/src/common/parse.rs:328

    } else if is_snapshot {
        BookAction::Add
    } else {
        BookAction::Update
    }
}

/// Parses a Nautilus bar specification from the given Tardis string `value`.
///
/// The [`PriceType`] is always `LAST` for Tardis trade bars.
///
/// # Errors
///
/// Returns an error if the specification format is invalid or if the aggregation suffix is unsupported.
pub fn parse_bar_spec(value: &str) -> anyhow::Result<BarSpecification> {
    let parts: Vec<&str> = value.split('_').collect();
    let last_part = parts
        .last()
        .ok_or_else(|| anyhow::anyhow!("Invalid bar spec: empty string"))?;
    let split_idx = last_part
        .chars()
        .position(|c| !c.is_ascii_digit())
        .ok_or_else(|| anyhow::anyhow!("Invalid bar spec: no aggregation suffix in '{value}'"))?;

    let (step_str, suffix) = last_part.split_at(split_idx);
    let step: usize = step_str
        .parse()
        .map_err(|e| anyhow::anyhow!("Invalid step in bar spec '{value}': {e}"))?;

    let aggregation = match suffix {
        "ms" => BarAggregation::Millisecond,
        "s" => BarAggregation::Second,
        "m" => BarAggregation::Minute,
        "ticks" => BarAggregation::Tick,
        "vol" => BarAggregation::Volume,
        _ => anyhow::bail!("Unsupported bar aggregation type: '{suffix}'"),
    };

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check where the bar spec string originates — an empty value usually means a missing config field or a bad split of a comma-separated list
  2. Provide a valid bar spec like "1-MINUTE" / "100-TICK" (step + suffix) in the subscription config
  3. Filter out empty strings before calling parse_bar_spec
  4. Validate bar specs at config-load time so the error surfaces early

Example fix

// before: empty entries slip through
for spec in csv_string.split(',') {
    let bar = parse_bar_spec(spec)?;
}
// after: skip empties
for spec in csv_string.split(',').map(str::trim).filter(|s| !s.is_empty()) {
    let bar = parse_bar_spec(spec)?;
}
Defensive patterns

Strategy: validation

Validate before calling

fn valid_bar_spec_input(value: &str) -> bool { !value.trim().is_empty() }

Try / catch

match parse_bar_spec(spec) {
    Ok(bar) => /* proceed */,
    Err(e) if e.to_string().contains("empty string") => {
        log::warn!("blank bar spec in config, using default '1m'");
        parse_bar_spec("1m")?
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling parse_bar_spec("") or a whitespace-only string; passing a None-derived/empty value from upstream config or a Tardis bar message with a missing spec field.

Common situations: Empty bar_spec entry in Tardis subscription config (e.g. trailing comma in a CSV list split into parts); bar spec field absent in a replayed instrument definition.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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