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,decimalWhat 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.