nautechsystems/nautilus_trader · error · anyhow::Error
prepare_custom_data_batch called with empty data
Error message
prepare_custom_data_batch called with empty data
What it means
prepare_custom_data_batch requires at least one CustomData item to derive the type name, identifier, metadata, and timestamp bounds from the first element. An empty Vec cannot produce a record batch, so it is rejected upfront rather than failing later during encoding.
Source
Thrown at crates/persistence/src/backend/custom.rs:136
for segment in safe.split('/') {
components.push(segment.to_string());
}
}
}
components
}
/// Prepares a batch of custom data for writing: encodes to Arrow, augments with `data_type` column,
/// and returns type identity and timestamp range so the catalog can build path and perform I/O.
///
/// # Errors
///
/// Returns an error if encoding or augmentation fails, or if the type is not registered.
pub fn prepare_custom_data_batch(
data: Vec<CustomData>,
) -> anyhow::Result<(RecordBatch, String, Option<String>, UnixNanos, UnixNanos)> {
let Some(first_custom) = data.first() else {
anyhow::bail!("prepare_custom_data_batch called with empty data");
};
let type_name = first_custom.data.type_name();
let identifier = first_custom.data_type.identifier().map(String::from);
let dt_meta = first_custom.data_type.metadata_string_map();
let data_type_json = first_custom
.data_type
.to_persistence_json()
.map_err(|e| anyhow::anyhow!("Failed to serialize data_type for persistence: {e}"))?;
let start_ts = first_custom.data.ts_init();
let end_ts = data.last().map_or(start_ts, |custom| custom.data.ts_init());
let items: Vec<Arc<dyn CustomDataTrait>> =
data.into_iter().map(|c| Arc::clone(&c.data)).collect();
let batch = encode_custom_to_arrow(type_name, &items)
.map_err(|e| anyhow::anyhow!("Failed to encode custom data to Arrow: {e}"))?
.ok_or_else(|| {View on GitHub (pinned to 18893faf8b)
Solutions
- Check data.is_empty() before calling write_custom_data_batch and return early when empty.
- Guard the call site so only non-empty batches are written.
- If empty writes should be valid, treat them as no-ops at the caller instead of invoking the API.
Example fix
// before
write_custom_data_batch(data)?;
// after
if !data.is_empty() {
write_custom_data_batch(data)?;
} Defensive patterns
Strategy: validation
Validate before calling
if data.is_empty() {
return; // nothing to write — skip the API call entirely
}
write_custom_data_batch(data)?; Try / catch
if let Err(e) = write_custom_data_batch(data) {
if e.to_string().contains("empty data") {
// treat as no-op; log and continue
} else {
return Err(e);
}
} Prevention
- Early-return on empty collections before any write API call.
- Check that upstream filters/queries actually produced rows before writing.
- Add an assertion/debug check on batch length in ingestion pipelines.
When it happens
Trigger: Calling write_custom_data_batch with an empty Vec<CustomData>; the writer passes the batch through to prepare_custom_data_batch which bails on data.first() being None.
Common situations: A query/backfill produced no rows but the code unconditionally calls the write API, a filtered stream yielded nothing for a session, or an off-by-one slice produced an empty collection.
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
- Unknown data type: {type_name}
- Unknown data type: {type_name}; custom decode only allowed i
- Failed to serialize data_type for persistence: {e}
- Failed to encode custom data to Arrow: {e}
- Custom data type "{type_name}" is not registered for Arrow e
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/91f2acbad0f18473.
Report an issue: GitHub.