use std::{fmt, str::FromStr};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", 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, Error> {
if (Self::MIN..=Self::MAX).contains(&code) {
Ok(Self(code))
} else {
Err(Error::InvalidPruefidentifikatorRange(code))
}
}
#[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 FromStr for Pruefidentifikator {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u32>()
.map_err(|_| Error::InvalidPruefidentifikatorFormat {
raw_value: s.to_owned(),
})
.and_then(Self::new)
}
}
#[must_use]
pub fn answer_pids(anfrage: u32) -> Option<(u32, u32)> {
Some(match anfrage {
55001 => (55002, 55003), 55004 => (55005, 55006), 55016 => (55017, 55018), 55077 => (55078, 55080), 44001 => (44002, 44003), 44004 => (44005, 44006), 44007 => (44008, 44009), 44010 => (44011, 44012), 44013 => (44014, 44015), 44016 => (44017, 44018), _ => return None,
})
}
#[must_use]
pub fn bestaetigung_pid(anfrage: u32) -> Option<u32> {
match anfrage {
44020 => Some(44021),
other => answer_pids(other).map(|(ok, _)| ok),
}
}
#[must_use]
pub fn ablehnung_pid(anfrage: u32) -> Option<u32> {
answer_pids(anfrage).map(|(_, nok)| nok)
}
#[cfg(test)]
mod answer_pid_tests {
use super::*;
#[test]
fn documented_deviations_from_the_plus_one_pattern_hold() {
assert_eq!(
answer_pids(55077),
Some((55078, 55080)),
"55079 is unassigned, so the Ablehnung is 55080 — not Anfrage+2"
);
assert_eq!(
bestaetigung_pid(44020),
Some(44021),
"44020 is confirmable even though it has no Ablehnung"
);
assert_eq!(ablehnung_pid(44020), None);
assert_eq!(answer_pids(44020), None, "no complete pair for 44020");
assert_eq!(answer_pids(44019), None, "44019 has neither answer");
}
#[test]
fn the_table_is_internally_consistent() {
let requests: Vec<u32> = (44000..=44999).chain(55000..=55999).collect();
let mut answers = Vec::new();
for anfrage in requests.iter().copied() {
let Some((ok, nok)) = answer_pids(anfrage) else {
continue;
};
assert!(Pruefidentifikator::new(ok).is_ok(), "{ok} constructible");
assert!(Pruefidentifikator::new(nok).is_ok(), "{nok} constructible");
assert_ne!(ok, nok, "{anfrage}: Bestätigung and Ablehnung must differ");
assert!(
ok > anfrage && nok > anfrage,
"{anfrage}: answers follow it"
);
answers.push(ok);
answers.push(nok);
}
assert!(!answers.is_empty(), "the table must not be empty");
for a in &answers {
assert!(
answer_pids(*a).is_none(),
"PID {a} is an answer — it must not also be a request"
);
}
let mut sorted = answers.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(
sorted.len(),
answers.len(),
"no PID may answer two different Anfragen"
);
}
}