BO4E v202607.1.0 · Rust 1.88+

BO4E, with the Rust type system doing the checking.

rubo4e generates the full BO4E object model from the official JSON Schema, then adds what the schema cannot express: market identifiers that verify their own BDEW check digits, enums you can parse strictly at an ingest boundary, and JSON that stays byte-compatible with the Python, Go, and .NET implementations.

Install

Nothing but identifier types is on by default. Every dependency past that is a feature you opt into.

# Identifier types only — no schema, no serde_json, no date/decimal deps.
cargo add rubo4e

# The usual application setup: generated types, JSON, real dates and decimals.
cargo add rubo4e --features versioned,json,time,decimal

What the schema cannot give you

Generated types get you the shape of the data. These are the parts that need domain rules from BDEW documents, not the JSON Schema.

Identifiers that verify themselves

A MaloId cannot exist with a bad check digit. The BDEW chapter-8 procedures are implemented once and shared by every identifier that uses them.

Strict parsing where it matters

Unknown enum values decode to Unknown so tomorrow's schema does not break today's build. At an ingest boundary, one call rejects them instead.

Wire-compatible JSON

German camelCase output is checked against payloads produced by the Python and Go implementations. Unknown fields survive a round-trip untouched.

Time series that audit themselves

A Lastgang is a bag of intervals the schema never says must line up. One call reports the gaps, the double-billed slots, and the readings marked absent — and turns kW into the kWh an invoice bills.

Hardened against untrusted input

Size, depth, and extension-data budgets are enforced during parsing at every nesting level — not after the object tree has already been allocated.

Fits your stack

Optional serde, sqlx, schemars, utoipa, garde, and typed-builder integrations. Enable only what you use.

Versioning you can pin

Import rubo4e::current to track the latest schema series, or a version module like rubo4e::v202607 where membership must not move.

A typical round-trip

Parse an incoming payload, reject anything outside the schema, then work with ordinary Rust types.

use rubo4e::current::Marktlokation;
use rubo4e::json::{Bo4eJsonExt, JsonParseLimits};
use rubo4e::Bo4eStrict;

// Parse with size, depth, and extension-data budgets enforced while reading.
let malo = Marktlokation::from_json_german_hardened(
    &body,
    JsonParseLimits::untrusted_defaults(),
)?;

// The serde path is deliberately lenient so a newer schema does not break this
// build. Opt into strictness where bad data must not get through.
malo.ensure_known_enums()?;

// From here it is ordinary Rust: the ID already verified its own check digit.
if let Some(id) = &malo.marktlokations_id {
    println!("{id}");
}

Documentation

Start with whichever question brought you here.

Architecture

Workspace layout, module tree, and the feature-gate taxonomy — plus where the boundary sits between this library and your application code.

Identifiers

Every BO4E market identifier as a validated newtype: MaLo-ID, MeLo-ID, the Zählpunktbezeichnung, EIC, OBIS, Marktpartner-ID, the Lokationsbündel codes, the Redispatch 2.0 resource IDs and the SEPA bank identifiers, with the check-digit procedures behind them.

Lokationsbündel

There is no Lokationsbuendel Geschäftsobjekt in BO4E — there is a Lokationszuordnung and two BDEW codes. The published EDI@Energy codelist, shipped as data, and an audit that checks a bundle against the structure it declares.

Serialization

German camelCase, snake_case, and canonical JSON output; round-trip preservation of unknown fields; and the limits that make parsing untrusted payloads safe.

Validation

The three independent validation layers — constructor invariants, garde-based cross-field rules, and JSON Schema — and when to reach for each.

Time Series & Units

Interval series and register series, one reading shape across Lastgang, Zeitreihe and Energiemenge, the coverage audit, Zeitraum's instant mode, and the unit dimensions behind sum, integrate and consumption.

Beyond the Schema

What rubo4e does when a market rule needs something BO4E has not modelled: the one test every addition passes, where each kind of addition lands, and BK6-20-160 Modell 2 worked through end to end as the example.

Schema Versioning

How BO4E schema releases map onto Rust modules, what rubo4e::current guarantees, and which imports to pin when enum membership must not move underneath you.

Ecosystem Integrations

Optional serde, sqlx, schemars, utoipa, strum, and proptest integrations — each behind a feature gate, each costing nothing when disabled.

Code Generator

How the internal generator turns pinned BO4E JSON Schema releases into Rust source, including type inference, identifier naming, and the drift checks that keep output honest.

Testing Strategy

The eight testing layers that back this crate: schema drift guards, golden corpus, snapshots, property tests, fuzzing, cross-implementation compatibility, doctests, and the feature matrix.