Multi-Schema Patterns

SBE schemas often share types (messageHeader, groupSizeEncoding, composites, enums, sets). ergo-sbe supports two approaches:

ApproachWhenMethod
xi:include (standard)Schema files live together; official SBE portability matters<include href="common-types.xml"/>parse_file resolves includes relative to the base dir
Shared Ir (programmatic)Schemas are parsed from strings, generated, or live in separate repos; no filesystem dependencyparse_with_shared / parse_file_with_shared — seed one parse from another's resolved types

The <include> path is what the SBE spec expects. The shared-Ir path is a convenience for tooling, build scripts, and any workflow where you already have the shared schema parsed in memory.

Shared Ir — parse, then share

// 1. Parse the shared schema once (composites, enums, sets).
let common = ergo_sbe::parse_file("schemas/common-types.xml")?;

// 2. Parse a consumer schema — no <include> needed.
let orders = ergo_sbe::parse_file_with_shared("schemas/orders.xml", &common)?;

// 3. Each schema gets its own module.
let generator = ergo_sbe::Generator::new(
    ergo_sbe::GenerationConfig::new("common_types").with_shared_module("common_types"),
);
let modules = generator.generate_multi(&[
    (&ergo_sbe::Schema::from_ir(common), "common_types"),
    (&ergo_sbe::Schema::from_ir(orders), "orders"),
])?;

With with_shared_module("common_types"), the first entry owns the shared enums/sets/composites; later entries pub use super::common_types::* and skip duplicate type generation.

Module names must be unique, non-empty Rust identifiers (no path separators or keywords). Before any file is written, shared types with the same name are compared by a canonical wire fingerprint (token order, primitive encodings, offsets, presence, null/min/max, discriminants/choices, sinceVersion, and schema byte order). A name collision with a different fingerprint fails generation with GenerateError::IncompatibleSharedType rather than silently reusing the first schema's layout.

parse_with_shared from in-memory strings

let common = ergo_sbe::parse(
    r#"<?xml version="1.0"?>
<messageSchema package="common" id="0" version="1" byteOrder="littleEndian">
  <types>
    <composite name="messageHeader">
      <type name="blockLength" primitiveType="uint16"/>
      <type name="templateId" primitiveType="uint16"/>
      <type name="schemaId" primitiveType="uint16"/>
      <type name="version" primitiveType="uint16"/>
    </composite>
    <composite name="Price">
      <type name="mantissa" primitiveType="int64"/>
      <type name="exponent" primitiveType="int8"/>
    </composite>
  </types>
</messageSchema>"#,
)?;

// No <types> / <include> — Price resolves from `common`.
let orders = ergo_sbe::parse_with_shared(
    r#"<?xml version="1.0"?>
<messageSchema package="orders" id="1" version="1" byteOrder="littleEndian"
               headerType="messageHeader">
  <message name="NewOrder" id="1">
    <field name="price" id="1" type="Price"/>
  </message>
</messageSchema>"#,
    &common,
)?;

The shared Ir path does not recover bare top-level <type> typedefs (those are inlined during parsing and dropped from the token stream). Reference them through a <composite> / <enum> / <set> in the shared schema instead.

Full build.rs — one helper call

Module names are supplied, not derived from file stems, so a hyphenated common-types.xml can emit common_types.rs. The helper parses the shared schema first, resolves consumers against it, validates the whole set, then writes. A late consumer failure leaves no partial files. Cargo watches every root and every resolved include.

// build.rs
use std::path::Path;
use ergo_sbe::{GenerationConfig, SchemaFile, generate_multi_to_out_dir};

fn main() -> ergo_sbe::miette::Result<()> {
    generate_multi_to_out_dir(
        SchemaFile::new(Path::new("schemas/common-types.xml"), "common_types"),
        &[
            SchemaFile::new(Path::new("schemas/orders.xml"), "orders"),
            SchemaFile::new(Path::new("schemas/fills.xml"), "fills"),
        ],
        GenerationConfig::new("common_types"),
    )?;
    Ok(())
}

Consumer modules import the shared module for cross-schema type resolution:

mod common_types { include!(concat!(env!("OUT_DIR"), "/common_types.rs")); }
mod orders {
    use super::common_types::*;       // shared types + header composite
    include!(concat!(env!("OUT_DIR"), "/orders.rs"));
}
mod fills {
    use super::common_types::*;
    include!(concat!(env!("OUT_DIR"), "/fills.rs"));
}

See also: sbe-codegen-examples (reusable generator setup), multi_schema_versioning_test (versioned schemas with shared types), exchange-example (multi-schema exchange feed with IPC).