bdew_ascii_identifier! {
NeloId,
prefix = b"E",
schema = "crate::schema_helpers::nelo_id_schema",
schema_meta = crate::identifiers::schema::NELO_ID,
pattern = r"^E[A-Z0-9]{9}[0-9]$",
expecting = "an 11-character BDEW Netzlokations-ID (Codetyp 'E')",
example = ("E000000001", "E0000000019"),
check_fn = check_nelo_id,
}
bdew_ascii_identifier! {
NebeId,
prefix = b"F",
schema = "crate::schema_helpers::nebe_id_schema",
schema_meta = crate::identifiers::schema::NEBE_ID,
pattern = r"^F[A-Z0-9]{9}[0-9]$",
expecting = "an 11-character BDEW Netzbereich-ID (Codetyp 'F')",
example = ("F000000001", "F0000000018"),
check_fn = check_nebe_id,
}
bdew_ascii_identifier! {
CrId,
prefix = b"A",
schema = "crate::schema_helpers::cr_id_schema",
schema_meta = crate::identifiers::schema::CR_ID,
pattern = r"^A[A-Z0-9]{9}[0-9]$",
expecting = "an 11-character BDEW Cluster-Ressource-ID (Codetyp 'A')",
example = ("A000000001", "A0000000013"),
check_fn = check_cr_id,
}
bdew_ascii_identifier! {
SgId,
prefix = b"B",
schema = "crate::schema_helpers::sg_id_schema",
schema_meta = crate::identifiers::schema::SG_ID,
pattern = r"^B[A-Z0-9]{9}[0-9]$",
expecting = "an 11-character BDEW Steuergruppen-ID (Codetyp 'B')",
example = ("B000000001", "B0000000012"),
check_fn = check_sg_id,
}
bdew_ascii_identifier! {
SrId,
prefix = b"C",
schema = "crate::schema_helpers::sr_id_schema",
schema_meta = crate::identifiers::schema::SR_ID,
pattern = r"^C[A-Z0-9]{9}[0-9]$",
expecting = "an 11-character BDEW Steuerbare-Ressource-ID (Codetyp 'C')",
example = ("C000000001", "C0000000011"),
check_fn = check_sr_id,
}
bdew_ascii_identifier! {
TrId,
prefix = b"D",
schema = "crate::schema_helpers::tr_id_schema",
schema_meta = crate::identifiers::schema::TR_ID,
pattern = r"^D[A-Z0-9]{9}[0-9]$",
expecting = "an 11-character BDEW Technische-Ressource-ID (Codetyp 'D')",
example = ("D000000001", "D0000000010"),
check_fn = check_tr_id,
}
bdew_ascii_identifier! {
PaketId,
prefix = b"P9",
schema = "crate::schema_helpers::paket_id_schema",
schema_meta = crate::identifiers::schema::PAKET_ID,
pattern = r"^P9[A-Z0-9]{8}[0-9]$",
expecting = "an 11-character BDEW Paket-ID (Codetyp 'P9')",
example = ("P900000001", "P9000000010"),
check_fn = check_paket_id,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::IdentifierError;
macro_rules! for_each_type {
($mac:ident) => {
$mac!(NeloId, "E", "E000000001");
$mac!(NebeId, "F", "F000000001");
$mac!(CrId, "A", "A000000001");
$mac!(SgId, "B", "B000000001");
$mac!(SrId, "C", "C000000001");
$mac!(TrId, "D", "D000000001");
$mac!(PaketId, "P9", "P900000001");
};
}
#[test]
fn codetyp_constant_matches_prefix() {
macro_rules! check {
($ty:ident, $codetyp:literal, $base:literal) => {
assert_eq!($ty::CODETYP, $codetyp);
assert!($base.starts_with($codetyp));
};
}
for_each_type!(check);
}
#[test]
fn from_base_round_trips_through_new() {
macro_rules! check {
($ty:ident, $codetyp:literal, $base:literal) => {
let id = $ty::from_base($base).expect("base is valid");
assert_eq!(id.as_ref().len(), 11);
assert_eq!(id.base(), $base);
assert_eq!($ty::new(id.as_ref()).unwrap(), id);
assert_eq!(id.to_string().parse::<$ty>().unwrap(), id);
};
}
for_each_type!(check);
}
#[test]
fn wrong_check_digit_is_rejected() {
macro_rules! check {
($ty:ident, $codetyp:literal, $base:literal) => {
let id = $ty::from_base($base).unwrap();
let correct = id.as_ref().as_bytes()[10] - b'0';
let wrong = (correct + 1) % 10;
let mutated = format!("{}{}", id.base(), wrong);
assert!(
matches!($ty::new(&mutated), Err(IdentifierError::InvalidChecksum)),
concat!(stringify!($ty), ": mutated check digit must be rejected"),
);
};
}
for_each_type!(check);
}
#[test]
fn wrong_codetyp_is_rejected() {
macro_rules! check {
($ty:ident, $codetyp:literal, $base:literal) => {
let foreign = format!("Z{}", &$base[1..]);
assert!(matches!(
$ty::from_base(&foreign),
Err(IdentifierError::InvalidFormat { .. })
));
};
}
for_each_type!(check);
}
#[test]
fn lowercase_and_wrong_length_are_rejected() {
macro_rules! check {
($ty:ident, $codetyp:literal, $base:literal) => {
let lower = format!("{}a00000001", $codetyp);
let lower = &lower[..10];
assert!(matches!(
$ty::from_base(lower),
Err(IdentifierError::InvalidCharacter { .. })
));
assert!(matches!(
$ty::new($base),
Err(IdentifierError::InvalidLength { .. })
));
};
}
for_each_type!(check);
}
#[test]
fn paket_id_requires_bdew_strom_digit() {
assert!(matches!(
PaketId::from_base("P800000001"),
Err(IdentifierError::InvalidFormat { .. })
));
assert!(PaketId::from_base("P900000001").is_ok());
}
#[test]
fn non_ascii_input_does_not_panic() {
assert!(NeloId::new("E00000000ä").is_err());
assert!(NeloId::new("Ü0000000019").is_err());
}
}