pub mod generators;
pub mod seed_data;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum RandomizeError {
#[error("no randomization function available for field '{bo4e_name}'")]
NoGeneratorForField { bo4e_name: String },
}
pub fn field_seed(base_seed: u64, bo4e_name: &str) -> u64 {
let mut h = base_seed;
for b in bo4e_name.bytes() {
h = h.wrapping_mul(1_099_511_628_211).wrapping_add(b as u64);
}
h
}
pub fn randomize_field(
bo4e_name: &str,
valid_codes: &[String],
format: Option<&str>,
seed: u64,
) -> Result<String, RandomizeError> {
use generators::*;
use seed_data::*;
match bo4e_name {
"marktlokationsId" => Ok(generate_malo_id(seed)),
"messlokationsId" => Ok(generate_melo_id(seed)),
"netzlokationsId" => Ok(generate_nelo_id(seed)),
"steuerbareRessourceId" => Ok(generate_steuress_id(seed)),
"technischeRessourceId" => Ok(generate_techress_id(seed)),
"tranchenId" => Ok(generate_malo_id(seed.wrapping_add(7))),
"identifikation" | "absenderCode" | "empfaengerCode" => Ok(generate_gln(seed)),
"interchangeRef" => Ok(generate_reference_id(seed)),
"datum" => Ok(generate_datum(seed)),
"zeit" => Ok(generate_zeit(seed)),
"nachname" => Ok(pick(NACHNAMEN, seed).to_string()),
"vorname" => Ok(pick(VORNAMEN, seed).to_string()),
"anrede" => Ok(pick(ANREDEN, seed).to_string()),
"titel" => Ok(pick(TITEL, seed).to_string()),
"strasse" => Ok(pick_address(seed).strasse.to_string()),
"hausnummer" => Ok(pick_address(seed).hausnummer.to_string()),
"ort" => Ok(pick_address(seed).ort.to_string()),
"postleitzahl" => Ok(pick_address(seed).plz.to_string()),
"region" => Ok(pick_address(seed).bundesland.to_string()),
"land" => Ok("DE".to_string()),
"vorgangId" => Ok(generate_reference_id(seed)),
_ if !valid_codes.is_empty() => {
let idx = seed as usize % valid_codes.len();
Ok(valid_codes[idx].clone())
}
_ if format == Some("date") => Ok(generate_date_ymd(seed)),
_ if bo4e_name.ends_with("datum")
|| bo4e_name.ends_with("Datum")
|| bo4e_name.ends_with("Ab")
|| bo4e_name.ends_with("Bis")
|| bo4e_name.ends_with("Von")
|| bo4e_name.starts_with("gueltig") =>
{
Ok(generate_date_ymd(seed))
}
_ if bo4e_name.ends_with("Referenz") || bo4e_name.ends_with("referenz") => {
Ok(generate_reference_id(seed))
}
_ => Err(RandomizeError::NoGeneratorForField {
bo4e_name: bo4e_name.to_string(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_malo_id_dispatch() {
let result = randomize_field("marktlokationsId", &[], None, 42).unwrap();
assert_eq!(result.len(), 11, "MaLo ID must be 11 digits");
assert!(result.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn test_melo_id_dispatch() {
let result = randomize_field("messlokationsId", &[], None, 42).unwrap();
assert!(result.starts_with("DE"));
assert_eq!(result.len(), 33);
}
#[test]
fn test_gln_dispatch() {
let result = randomize_field("absenderCode", &[], None, 42).unwrap();
assert_eq!(result.len(), 13);
}
#[test]
fn test_datum_dispatch() {
let result = randomize_field("datum", &[], None, 42).unwrap();
assert_eq!(result.len(), 6, "datum must be YYMMDD");
assert!(result.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn test_zeit_dispatch() {
let result = randomize_field("zeit", &[], None, 42).unwrap();
assert_eq!(result.len(), 4, "zeit must be HHMM");
}
#[test]
fn test_enum_pick_dispatch() {
let codes = vec!["E01".to_string(), "ZW4".to_string(), "E03".to_string()];
let result = randomize_field("someEnumField", &codes, None, 0).unwrap();
assert!(codes.contains(&result), "result must be from valid_codes");
}
#[test]
fn test_enum_pick_uniform() {
let codes = vec!["A".to_string(), "B".to_string(), "C".to_string()];
let r0 = randomize_field("field", &codes, None, 0).unwrap();
let r1 = randomize_field("field", &codes, None, 1).unwrap();
let r2 = randomize_field("field", &codes, None, 2).unwrap();
assert_eq!(r0, "A"); assert_eq!(r1, "B"); assert_eq!(r2, "C"); }
#[test]
fn test_date_format_dispatch() {
let result = randomize_field("someDate", &[], Some("date"), 42).unwrap();
assert_eq!(result.len(), 10, "date_ymd must be YYYY-MM-DD, got: {result}");
assert!(result.contains('-'));
}
#[test]
fn test_referenz_suffix_dispatch() {
let result = randomize_field("nachrichtenReferenz", &[], None, 42).unwrap();
assert_eq!(result.len(), 12, "reference must be 12 chars");
assert!(!result.contains('+'));
assert!(!result.contains(':'));
}
#[test]
fn test_referenz_with_valid_codes_picks_code_not_reference() {
let codes = vec!["E01".to_string(), "E02".to_string()];
let result = randomize_field("nachrichtenReferenz", &codes, None, 0).unwrap();
assert!(codes.contains(&result), "must pick from valid_codes, got: {result}");
}
#[test]
fn test_datum_suffix_dispatch() {
let result = randomize_field("lieferdatum", &[], None, 42).unwrap();
assert_eq!(result.len(), 10, "must be YYYY-MM-DD, got: {result}");
assert!(result.contains('-'));
}
#[test]
fn test_bis_suffix_dispatch() {
let result = randomize_field("gueltigkeitsBis", &[], None, 42).unwrap();
assert_eq!(result.len(), 10, "must be YYYY-MM-DD, got: {result}");
}
#[test]
fn test_gueltig_prefix_dispatch() {
let result = randomize_field("gueltigAb", &[], None, 42).unwrap();
assert_eq!(result.len(), 10, "must be YYYY-MM-DD, got: {result}");
}
#[test]
fn test_unknown_field_error() {
let err = randomize_field("unknownField", &[], None, 42).unwrap_err();
assert!(err.to_string().contains("unknownField"));
}
#[test]
fn test_field_seed_deterministic() {
let s1 = field_seed(42, "marktlokationsId");
let s2 = field_seed(42, "marktlokationsId");
assert_eq!(s1, s2);
}
#[test]
fn test_field_seed_different_fields() {
let s1 = field_seed(42, "marktlokationsId");
let s2 = field_seed(42, "messlokationsId");
assert_ne!(s1, s2);
}
#[test]
fn test_randomize_field_deterministic() {
let r1 = randomize_field("marktlokationsId", &[], None, 99).unwrap();
let r2 = randomize_field("marktlokationsId", &[], None, 99).unwrap();
assert_eq!(r1, r2);
}
}