rubo4e 0.4.0

Rust implementation of the BO4E energy-market data standard
Documentation

rubo4e ⚡

A Rust implementation of the BO4E energy-market data standard — the canonical data model for the German energy industry.

⚠️ This is not an official BO4E implementation. The official reference implementation is BO4E-python. This crate aims for idiomatic Rust ergonomics, strong domain types, and ecosystem integration.

Crates.io License: MIT License: Apache 2.0 Rust 1.87+


✨ Features

  • 🏗️ Generated types from the official BO4E JSON Schema (v202501)
  • 🔒 Strong domain identifiers — MaloId, MeloId, EicCode, ObisCode, … with embedded validation
  • ✅ Three-layer validation — constructor checks, garde struct rules, cross-field business logic
  • 🔧 Typed builders — compile-time required-field enforcement via typed-builder; optional-field setters accept both T and Option<T>
  • 🌍 German / English / Canonical JSON — BO4E wire format out of the box
  • 📐 JSON Schema via schemars, OpenAPI via utoipa, DB via sqlx
  • 🧪 Golden corpus and fuzz harnesses included; proptest round-trip tests run as dev tests

📦 Installation

Add to your Cargo.toml:

[dependencies]
rubo4e = "0.3"

Enable optional features as needed:

rubo4e = { version = "0.3", features = ["json", "versioned", "validate", "builder"] }

🚀 Quick Start

use rubo4e::prelude::*;
use rubo4e::v202501::{Vertrag, Sparte};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Builder with compile-time required-field enforcement (requires `builder` feature)
    let vertrag = Vertrag::builder()
        .sparte(Sparte::Strom)
        .beschreibung("Jahresvertrag Strom".to_string())
        .vertragsnummer("VN-2025-001".to_string())
        .build();

    // Cross-field struct validation (requires `validate` feature)
    use garde::Validate as _;
    vertrag.validate()?;

    // German camelCase JSON — BO4E wire format (requires `json` feature)
    let json = vertrag.to_json_german()?;
    println!("{json}");

    Ok(())
}

🎛️ Feature Gates

Feature Default Description
serde ✓ Serde derives + extension-data map
json serde_json helpers (to_json_german(), …)
simd-json SIMD-accelerated JSON parsing backend
time time crate for timestamps
decimal rust_decimal::Decimal for amounts and prices
builder typed-builder derives
validate garde validation
schemars JSON Schema generation
sqlx sqlx type integrations (PostgreSQL)
utoipa utoipa OpenAPI integration
strum Enum iteration and string conversion
versioned Versioned schema modules (v202501, current)
tracing Structured diagnostics via the tracing crate
metrics Counter export hooks (metrics ecosystem)

🗂️ Schema Versions

Module Status
v202501 ✅ Current stable

Use the versioned module to pin a stable schema:

use rubo4e::v202501::Marktlokation;  // pin to v202501
use rubo4e::current::Marktlokation;  // always the latest stable — advances with crate updates

🏷️ Identifiers

All domain identifiers validate their format on construction:

Type Format / Rule
MaloId 11 digits, BDEW alternating-weight checksum
SrId 11 digits, BDEW alternating-weight checksum (same algorithm as MaloId)
TrId 11 digits, BDEW alternating-weight checksum (same algorithm as MaloId)
MeloId 33 characters: 2-char ISO country code + 31 alphanumeric
NeloId 11 alphanumeric characters
EicCode 16-character EIC with check character (types A, T, V, W, X, Y, Z)
ObisCode OBIS identifier (e.g. 1-0:1.8.1); C ≥ 1 enforced
MarktpartnerId 13 decimal digits, no checksum

Identifier Utilities

Beyond construction, identifiers expose domain-specific helpers:

// Compute check digit / build from base (MaloId, SrId, TrId)
let check = MaloId::check_digit("5123869678")?;        // → 0
let id    = MaloId::from_base("5123869678")?;          // → "51238696780"

// Country code extraction (MeloId)
let melo = MeloId::new("DE0000123456789012345678901234561")?;
assert_eq!(melo.country_code(), "DE");
assert!(melo.is_german());

// Integer round-trip for legacy systems (MarktpartnerId)
let mp = MarktpartnerId::new("9900357000004")?;
assert_eq!(mp.to_i64(), 9_900_357_000_004_i64);

// Serde as integer instead of string (opt-in, field-level)
#[serde(with = "rubo4e::identifiers::marktpartner_id_as_i64")]
pub partner_id: MarktpartnerId,

Convenience Methods on Generated Types

The convenience module adds ergonomic helpers on generated BO types (requires versioned + time features):

use rubo4e::v202501::{Rechnung, PreisblattNetznutzung};

// Rechnung — closed billing period
if let Some((from, to)) = rechnung.billing_period() {
    println!("Invoice period: {from} – {to}");
}

// PreisblattNetznutzung — open-ended or closed validity
match preisblatt.validity() {
    Some((start, Some(end))) => println!("valid {start} – {end}"),
    Some((start, None))      => println!("valid from {start} (open-ended)"),
    None                     => println!("validity unknown"),
}

// Zeitraum — low-level range helpers (also available on all types with gueltigkeit)
let z: Zeitraum = todo!();
let closed    = z.as_closed_range();     // Option<(Date, Date)>
let half_open = z.as_half_open_range();  // Option<(Date, Option<Date>)>

📚 Documentation


🔗 Related Projects

Project Language Notes
BO4E-python Python Official reference implementation
BO4E-Schemas JSON Schema Canonical schema source
go-bo4e Go Most mature non-Python implementation
bo4e-rust Rust Hochfrequenz's Rust implementation

📜 License

Dual-licensed under MIT or Apache 2.0 — your choice.

The BO4E standard itself is maintained by the Interessengemeinschaft Geschäftsobjekte Energiewirtschaft e. V..