use serde::{Deserialize, Serialize};
use std::fmt;
macro_rules! domain_id {
(
$(#[$attr:meta])*
$name:ident,
$doc:literal
) => {
$(#[$attr])*
#[doc = $doc]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(Box<str>);
impl $name {
#[must_use]
pub fn new(s: impl Into<Box<str>>) -> Self {
Self(s.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for $name {
fn from(s: String) -> Self {
Self(s.into_boxed_str())
}
}
impl From<&str> for $name {
fn from(s: &str) -> Self {
Self(s.into())
}
}
impl From<$name> for String {
fn from(id: $name) -> Self {
id.0.into()
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
&self.0
}
}
};
}
domain_id!(
MaLo,
"Marktlokations-ID — supply point identifier (EIC / MaLo format)"
);
domain_id!(
MeLo,
"Messlokations-ID — metering point identifier"
);
domain_id!(
MarktpartnerCode,
"Marktpartner-Code — BDEW code (293), GS1 GLN (9), or EIC (305) market-participant identifier"
);
domain_id!(
MessageRef,
"EDIFACT message reference (BGM/C106 document number)"
);
domain_id!(
DeviceId,
"Geräte-ID — physical metering device identifier (Zählernummer)"
);
domain_id!(
BkvId,
"Bilanzkreisverantwortlicher-ID — balance circle responsible party"
);
domain_id!(
UenbId,
"Übertragungsnetzbetreiber-ID — transmission grid operator identifier"
);
domain_id!(
BikoId,
"Bilanzkoordinator-ID — balance coordinator identifier (BIKO)"
);
domain_id!(
BillingPeriod,
"Abrechnungszeitraum — billing period identifier string"
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Pruefidentifikator(u32);
impl Pruefidentifikator {
pub const MIN: u32 = 10_000;
pub const MAX: u32 = 99_999;
pub fn new(code: u32) -> Result<Self, String> {
if (Self::MIN..=Self::MAX).contains(&code) {
Ok(Self(code))
} else {
Err(format!(
"invalid Pruefidentifikator {code}: must be a 5-digit code in 10000–99999"
))
}
}
#[must_use]
pub fn as_u32(self) -> u32 {
self.0
}
}
impl fmt::Display for Pruefidentifikator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:05}", self.0)
}
}
impl std::str::FromStr for Pruefidentifikator {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u32>()
.map_err(|_| format!("Pruefidentifikator is not a decimal integer: {s:?}"))
.and_then(Self::new)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn malo_roundtrip_display_and_serde() {
let m = MaLo::new("DE00123456789012345678901234567890");
assert_eq!(m.to_string(), "DE00123456789012345678901234567890");
let v = serde_json::to_value(&m).unwrap();
assert_eq!(v, json!("DE00123456789012345678901234567890"));
let back: MaLo = serde_json::from_value(v).unwrap();
assert_eq!(back, m);
}
#[test]
fn from_string_and_str() {
let from_string: MarktpartnerCode = MarktpartnerCode::from(String::from("4012345000009"));
let from_str: MarktpartnerCode = MarktpartnerCode::from("4012345000009");
assert_eq!(from_string, from_str);
}
#[test]
fn into_string() {
let mid = MessageRef::new("UTILMD-2025-001");
let s: String = mid.into();
assert_eq!(s, "UTILMD-2025-001");
}
#[test]
fn distinct_types_are_not_interchangeable() {
let malo_val = MaLo::new("A");
let messlokation = MeLo::new("A");
let _: MaLo = malo_val;
let _: MeLo = messlokation;
}
#[test]
fn as_str_and_as_ref() {
let g = MarktpartnerCode::new("4012345000009");
assert_eq!(g.as_str(), "4012345000009");
let s: &str = g.as_ref();
assert_eq!(s, "4012345000009");
}
}