nautechsystems/nautilus_trader · error · anyhow::Error

Expected Custom data variant

Error message

Expected Custom data variant

What it means

Generated by the `#[custom_data]` macro's `TryFrom<Data>` impl: the incoming `Data` enum was not the `Custom` variant at all (e.g. `Data::Instrument`, `Data::Trade`, `Data::OrderBookDeltas`), so conversion to the concrete custom type is impossible. The macro bails early instead of attempting a downcast on a non-custom payload.

Source

Thrown at crates/persistence/macros/src/custom.rs:1164

        impl #generics std::convert::From<#name #generics> for nautilus_model::data::Data {
            fn from(value: #name #generics) -> Self {
                nautilus_model::data::Data::Custom(nautilus_model::data::CustomData::from_arc(std::sync::Arc::new(value)))
            }
        }
    };
    let try_from_impl = quote! {
        impl #generics std::convert::TryFrom<nautilus_model::data::Data> for #name #generics {
            type Error = anyhow::Error;
            fn try_from(value: nautilus_model::data::Data) -> std::result::Result<Self, Self::Error> {
                match value {
                    nautilus_model::data::Data::Custom(custom) => {
                        if let Some(c) = custom.data.as_any().downcast_ref::<Self>() {
                            Ok(std::clone::Clone::clone(c))
                        } else {
                            anyhow::bail!("Expected {}", #name_str)
                        }
                    }
                    _ => anyhow::bail!("Expected Custom data variant"),
                }
            }
        }
    };
    (catalog_path_prefix_impl, from_impl, try_from_impl)
}

#[expect(
    clippy::too_many_lines,
    reason = "PyO3 token generation is clearer with related methods kept together"
)]
fn gen_pymethods_impl(ctx: &ExpansionContext<'_>) -> TokenStream {
    let name = ctx.name;
    let generics = ctx.generics;
    let field_list = ctx.field_list;
    if !ctx.options.pyo3 {
        return quote! {};
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Match on the `Data` enum first and only convert `Data::Custom` payloads; handle other variants separately.
  2. Filter or partition the buffer by variant/type before attempting the custom conversion.
  3. Check upstream code that constructs the `Data` values to confirm the correct variant is produced.

Example fix

// before
let typed = MyType::try_from(data)?;
// after
let typed = match data {
    nautilus_model::data::Data::Custom(_) => MyType::try_from(data)?,
    _ => return Ok(None), // skip non-custom data
};
Defensive patterns

Strategy: type-guard

Validate before calling

fn is_custom(data: &nautilus_model::data::Data) -> bool {
    matches!(data, nautilus_model::data::Data::Custom(_))
}

Type guard

fn as_custom(data: &nautilus_model::data::Data) -> Option<&nautilus_model::data::CustomData> {
    match data {
        nautilus_model::data::Data::Custom(c) => Some(c),
        _ => None,
    }
}

Try / catch

match MyType::try_from(data) {
    Ok(t) => handle(t),
    Err(e) if e.to_string() == "Expected Custom data variant" => Ok(()), // expected skip
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Passing a standard (non-custom) `Data` enum value into a macro-generated `TryFrom<T>` for a custom data type, e.g. feeding quote/trade data into a custom-data decode path or reading a buffer that mixes standard and custom data into the wrong consumer.

Common situations: A caller iterating a mixed `Data` buffer assumes all elements are its custom type; misrouted message channels delivering standard market data to a custom-data handler; test fixtures populating buffers with the wrong variant.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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