nautechsystems/nautilus_trader · error
Invalid topic format: empty topic
Error message
Invalid topic format: empty topic
What it means
`parse_topic` splits a Bybit WebSocket topic string on '.' and rejects input that produces no parts. In practice `str::split` never yields an empty vec, so this is a defensive guard against empty/invalid topic input reaching the kline parser.
Source
Thrown at crates/adapters/bybit/src/websocket/parse.rs:184
return serde_json::from_value(value.clone()).map_or_else(
|_| BybitWsFrame::Unknown(value),
BybitWsFrame::AccountPosition,
);
}
}
BybitWsFrame::Unknown(value)
}
/// Parses a Bybit WebSocket topic string into its components.
///
/// # Errors
///
/// Returns an error if the topic format is invalid.
pub fn parse_topic(topic: &str) -> anyhow::Result<Vec<&str>> {
let parts: Vec<&str> = topic.split('.').collect();
if parts.is_empty() {
anyhow::bail!("Invalid topic format: empty topic");
}
Ok(parts)
}
/// Parses a Bybit kline topic into (interval, symbol).
///
/// Topic format: "kline.{interval}.{symbol}" (e.g., "kline.5.BTCUSDT")
///
/// # Errors
///
/// Returns an error if the topic format is invalid.
pub fn parse_kline_topic(topic: &str) -> anyhow::Result<(&str, &str)> {
let kline = BybitWsPublicChannel::Kline.as_ref();
let parts = parse_topic(topic)?;
if parts.len() != 3 || parts[0] != kline {
anyhow::bail!(
"Invalid kline topic format: expected '{kline}.{{interval}}.{{symbol}}', was '{topic}'"
);View on GitHub (pinned to 18893faf8b)
Solutions
- Filter incoming WS messages: only pass frames whose `topic` field is a non-empty string into `parse_kline_topic`.
- Check that the message is actually a kline channel frame before parsing (match on topic prefix).
- Log and skip unexpected frames (pong/ack) instead of routing them to topic parsing.
Example fix
// before
if let Some(topic) = msg.topic {
let (interval, symbol) = parse_kline_topic(&topic)?;
}
// after
if let Some(topic) = msg.topic {
if !topic.is_empty() && topic.starts_with("kline") {
let (interval, symbol) = parse_kline_topic(&topic)?;
}
} Defensive patterns
Strategy: validation
Validate before calling
fn is_parseable_topic(topic: &str) -> bool {
!topic.is_empty()
} Type guard
fn non_empty_topic(topic: Option<&str>) -> Option<&str> {
topic.filter(|t| !t.is_empty())
} Try / catch
match parse_kline_topic(topic) {
Ok((interval, symbol)) => process_kline(interval, symbol, msg),
Err(e) => tracing::debug!("skipping frame with unusable topic: {e}"),
} Prevention
- Filter WS frames without a non-empty `topic` before parsing
- Handle ping/pong/ack frames separately from data frames
- Dispatch on topic prefix before delegating to specific parsers
When it happens
Trigger: Calling `parse_topic` (or `parse_kline_topic`, its only caller) with an empty or malformed topic string, typically when `handle_ws_message` receives a message whose `topic` field is empty or missing.
Common situations: A Bybit public WebSocket message arrives with an empty `topic` field (e.g. handshake/ack frames, ping/pong payloads, or a malformed frame) and is fed into kline topic parsing.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- missing price component in {label} level
- missing size component in {label} level
- invalid {field} `{raw}`
- invalid Bybit trigger type: '{s}', expected LastPrice, MarkP
- invalid Bybit TP/SL order type: '{s}', expected Market or Li
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/ce8557a245d6b7ed.
Report an issue: GitHub.