rubo4e 0.2.0

Rust implementation of the BO4E energy-market data standard
Documentation
# Ecosystem Integrations

`rubo4e` provides optional integrations with common Rust ecosystem crates.
Every integration is behind a feature gate and adds zero overhead when disabled.

> **Scope:** This library provides **types**. It does not contain HTTP handler code,
> Axum extractors, Actix-web guards, or any framework-specific glue code.
> Consumers compose `rubo4e` types with their own HTTP and persistence layers.

---

## schemars — JSON Schema Generation

**Feature flag:** `schemars`  
**Dependency:** `schemars = "1"`

Derive `JsonSchema` on all types to generate JSON Schema documents from Rust code.
Useful for API documentation, input validation pipelines, and tooling integration.

```toml
rubo4e = { version = "...", features = ["schemars"] }
```

```rust
use schemars::{schema_for, JsonSchema};

let schema = schema_for!(rubo4e::v202501::Vertrag);
let json = serde_json::to_string_pretty(&schema)?;
println!("{json}");
```

Identifier types appear as `{ "type": "string" }` in the schema — not as JSON objects.
This matches the wire format and keeps schemas interoperable with non-Rust consumers.

---

## sqlx — Database Type Impls

**Feature flag:** `sqlx`  
**Dependency:** `sqlx = "0.8"`  
**Primary target:** PostgreSQL (TEXT column type)

Store and query BO4E identifiers and enums directly in SQL queries without manual
string conversion.

```toml
rubo4e = { version = "...", features = ["sqlx"] }
```

### Identifier Storage

```rust
// MaloId stored as TEXT; validation runs on decode
sqlx::query!("INSERT INTO locations (malo_id) VALUES ($1)", malo_id as _)
    .execute(&pool).await?;

let row = sqlx::query!("SELECT malo_id FROM locations WHERE id = $1", id)
    .fetch_one(&pool).await?;
let malo: MaloId = row.malo_id.parse()?;  // validates on retrieval
```

### Enum Storage

BO4E enums are stored as their variant name string (e.g. `Sparte::Strom` → `"STROM"`).

```rust
sqlx::query!("SELECT sparte FROM contracts WHERE id = $1", id)
    .fetch_one(&pool)
    .await?;
// Decoding an unknown string from DB returns sqlx::Error::Decode
```

**Implemented for:** `MaloId`, `MeloId`, `NeloId`, `SrId`, `TrId`, `EicCode`,
`ObisCode`, `MarktpartnerId`, and key enums (`Sparte`, `BoTyp`, `Kundentyp`, `Tarifart`, …).

---

## utoipa — OpenAPI Schema Derivation

**Feature flag:** `utoipa`  
**Dependency:** `utoipa = "5"`

Derive `ToSchema` on all BO, COM, enum, and identifier types to auto-generate
OpenAPI/Swagger documentation from Rust types.

```toml
rubo4e = { version = "...", features = ["utoipa"] }
```

```rust
use utoipa::OpenApi;

#[derive(OpenApi)]
#[openapi(components(schemas(rubo4e::v202501::Vertrag)))]
struct ApiDoc;

let openapi = ApiDoc::openapi();
```

Property names in the generated OpenAPI schema use German camelCase, consistent
with the serde rename attributes and the BO4E wire format.

---

## strum — Enum Display + FromStr

**Feature flag:** `strum`  
**Dependency:** `strum = "0.28"` (with `derive` feature)

Convert BO4E enum values to and from strings without a `match` expression.
Useful for logging, debug output, CSV export, and non-JSON serialization.

```toml
rubo4e = { version = "...", features = ["strum"] }
```

```rust
// Display — produces the BO4E SCREAMING_SNAKE_CASE wire value
assert_eq!(Sparte::Strom.to_string(), "STROM");

// FromStr — accepts the same wire value
let sparte: Sparte = "STROM".parse()?;
assert_eq!(sparte, Sparte::Strom);

// Static str reference (zero allocation)
let s: &'static str = Sparte::Strom.into();
```

Without the `strum` feature, `Sparte` does not implement `Display` or `FromStr`.
Serde-based JSON serialization works independently of `strum`.

### Unknown Variant

All enums include a catch-all `Unknown` variant (serializes as `"UNKNOWN"`)
that captures unrecognised values for forward-compatibility.

### Error Handling

```rust
match "INVALID_SPARTE".parse::<Sparte>() {
    Err(strum::ParseError::VariantNotFound) => { /* handle */ }
    Ok(sparte) => { /* ... */ }
}
```

---

## proptest — Property Testing

`proptest` is a **dev-dependency** of `rubo4e`. No feature flag is needed.

To write property tests against BO4E-integrated code in your own crate, add both as
dev-dependencies:

```toml
# In your crate's Cargo.toml:
[dev-dependencies]
rubo4e = { version = "...", features = ["versioned", "serde"] }
proptest = "1"
```

Your property tests can use the same inline strategy pattern:

```rust
use proptest::prelude::*;

// Build a valid 11-digit BDEW ID string with the correct checksum.
fn valid_malo_id() -> impl Strategy<Value = String> {
    // ... construct a valid strategy
}

proptest! {
    #[test]
    fn my_service_handles_valid_malo(s in valid_malo_id()) {
        let malo = MaloId::new(&s).unwrap();
        let result = my_service.process_location(malo);
        prop_assert!(result.is_ok());
    }
}
```

See `tests/proptest_roundtrips.rs` in the `rubo4e` source for reference strategy
implementations.