use std::collections::HashMap;
#[derive(Debug, Default, Clone)]
pub struct PidRouter {
table: HashMap<u32, Box<str>>,
registered_by: HashMap<u32, Box<str>>,
}
impl PidRouter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, pid: u32, workflow_name: impl Into<Box<str>>) {
let wf = workflow_name.into();
self.table.insert(pid, wf);
}
pub fn register_with_module(
&mut self,
pid: u32,
workflow_name: impl Into<Box<str>>,
module: &str,
) {
let wf = workflow_name.into();
if let Some(existing_wf) = self.table.get(&pid) {
if *existing_wf != wf {
let existing_mod = self
.registered_by
.get(&pid)
.map_or("<unknown>", Box::as_ref);
panic!(
"PID {pid} routing conflict:\n \
module '{module}' tried to register PID {pid} → '{wf}'\n \
but it was already registered → '{existing_wf}' by module '{existing_mod}'\n \
Hint: use DeploymentRoles to prevent conflicting modules from \
both registering shared PIDs (e.g. 19001/19002 are claimed by \
gpke-konfiguration for NB role and wim-geraeteubernahme for nMSB role).\n \
Set EngineBuilder::with_deployment_roles(DeploymentRoles::nb()) to keep \
only the NB-role registration."
);
}
}
self.table.insert(pid, wf);
self.registered_by.insert(pid, module.into());
}
#[must_use]
pub fn route(&self, pid: u32) -> Option<&str> {
self.table.get(&pid).map(Box::as_ref)
}
pub fn registered_pids(&self) -> impl Iterator<Item = u32> + '_ {
self.table.keys().copied()
}
#[must_use]
pub fn len(&self) -> usize {
self.table.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_registered_pid() {
let mut r = PidRouter::new();
r.register(55001, "GpkeSupplierChange");
assert_eq!(r.route(55001), Some("GpkeSupplierChange"));
}
#[test]
fn route_unregistered_pid_returns_none() {
let r = PidRouter::new();
assert_eq!(r.route(55001), None);
}
#[test]
fn register_overwrites_previous_mapping() {
let mut r = PidRouter::new();
r.register(55001, "OldWorkflow");
r.register(55001, "NewWorkflow");
assert_eq!(r.route(55001), Some("NewWorkflow"));
assert_eq!(r.len(), 1);
}
#[test]
fn registered_pids_covers_all_entries() {
let mut r = PidRouter::new();
r.register(55001, "A");
r.register(55002, "B");
r.register(11001, "C");
let mut pids: Vec<u32> = r.registered_pids().collect();
pids.sort_unstable();
assert_eq!(pids, [11001, 55001, 55002]);
}
#[test]
fn multiple_pids_same_workflow() {
let mut r = PidRouter::new();
r.register(55001, "GpkeSupplierChange");
r.register(55002, "GpkeSupplierChange");
r.register(55003, "GpkeSupplierChange");
assert_eq!(r.len(), 3);
for pid in [55001, 55002, 55003] {
assert_eq!(r.route(pid), Some("GpkeSupplierChange"));
}
}
#[test]
fn is_empty_and_len() {
let mut r = PidRouter::new();
assert!(r.is_empty());
r.register(55001, "W");
assert!(!r.is_empty());
assert_eq!(r.len(), 1);
}
}