{"record":{"id":"e19b9298ecb277c8","repo":"nautechsystems/nautilus_trader","slug":"invalid-bar-spec-empty-string","errorCode":null,"errorMessage":"Invalid bar spec: empty string","messagePattern":"Invalid bar spec: empty string","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/tardis/src/common/parse.rs","lineNumber":328,"sourceCode":"    } else if is_snapshot {\n        BookAction::Add\n    } else {\n        BookAction::Update\n    }\n}\n\n/// Parses a Nautilus bar specification from the given Tardis string `value`.\n///\n/// The [`PriceType`] is always `LAST` for Tardis trade bars.\n///\n/// # Errors\n///\n/// Returns an error if the specification format is invalid or if the aggregation suffix is unsupported.\npub fn parse_bar_spec(value: &str) -> anyhow::Result<BarSpecification> {\n    let parts: Vec<&str> = value.split('_').collect();\n    let last_part = parts\n        .last()\n        .ok_or_else(|| anyhow::anyhow!(\"Invalid bar spec: empty string\"))?;\n    let split_idx = last_part\n        .chars()\n        .position(|c| !c.is_ascii_digit())\n        .ok_or_else(|| anyhow::anyhow!(\"Invalid bar spec: no aggregation suffix in '{value}'\"))?;\n\n    let (step_str, suffix) = last_part.split_at(split_idx);\n    let step: usize = step_str\n        .parse()\n        .map_err(|e| anyhow::anyhow!(\"Invalid step in bar spec '{value}': {e}\"))?;\n\n    let aggregation = match suffix {\n        \"ms\" => BarAggregation::Millisecond,\n        \"s\" => BarAggregation::Second,\n        \"m\" => BarAggregation::Minute,\n        \"ticks\" => BarAggregation::Tick,\n        \"vol\" => BarAggregation::Volume,\n        _ => anyhow::bail!(\"Unsupported bar aggregation type: '{suffix}'\"),\n    };","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/tardis/src/common/parse.rs#L310-L346","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Provide a valid bar spec like \"1-MINUTE\" / \"100-TICK\" (step + suffix) in the subscription config","Filter out empty strings before calling parse_bar_spec","Validate bar specs at config-load time so the error surfaces early"],"exampleFix":"// before: empty entries slip through\nfor spec in csv_string.split(',') {\n    let bar = parse_bar_spec(spec)?;\n}\n// after: skip empties\nfor spec in csv_string.split(',').map(str::trim).filter(|s| !s.is_empty()) {\n    let bar = parse_bar_spec(spec)?;\n}","handlingStrategy":"validation","validationCode":"fn valid_bar_spec_input(value: &str) -> bool { !value.trim().is_empty() }","typeGuard":null,"tryCatchPattern":"match parse_bar_spec(spec) {\n    Ok(bar) => /* proceed */,\n    Err(e) if e.to_string().contains(\"empty string\") => {\n        log::warn!(\"blank bar spec in config, using default '1m'\");\n        parse_bar_spec(\"1m\")?\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Filter empty entries when splitting CSV-style spec lists","Validate bar specs at config-load time","Always provide an explicit bar spec for Tardis subscriptions"],"tags":["tardis","bar-spec","parsing","validation"],"backgroundTag":"empty-required-field","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}