Skip to main content

sparkplug_b/
alias.rs

1//! Alias registry: the name ↔ alias ↔ datatype bindings established by a BIRTH.
2//!
3//! BIRTH metrics carry name + alias + datatype; later DATA/CMD metrics may carry
4//! only an alias and omit the datatype (`tck-id-payloads-metric-datatype-not-req`).
5//! Decoding such a payload requires recovering the datatype from this registry
6//! (built from the birth). Unlike Tahu's `MetricDataTypeMap`, [`clear`](AliasRegistry::clear)
7//! clears every map (Tahu's leaves the alias map populated — a known leak).
8
9use std::collections::HashMap;
10
11use crate::datatype::DataType;
12use crate::error::{Result, SparkplugError};
13
14/// A lookup key: either a metric name or an alias.
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
16pub enum MetricKey {
17    /// Look up by metric name.
18    Name(String),
19    /// Look up by alias.
20    Alias(u64),
21}
22
23/// Bidirectional name↔alias bindings plus a datatype index, scoped to one
24/// Edge Node or Device.
25#[derive(Clone, Debug, Default)]
26pub struct AliasRegistry {
27    name_to_alias: HashMap<String, u64>,
28    alias_to_name: HashMap<u64, String>,
29    dt_by_name: HashMap<String, DataType>,
30    dt_by_alias: HashMap<u64, DataType>,
31}
32
33impl AliasRegistry {
34    /// An empty registry.
35    #[must_use]
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Bind a metric's name (and optional alias) to its datatype, as declared in
41    /// a BIRTH. Overwrites any existing binding for the same name/alias; use
42    /// [`AliasRegistry::try_bind`] to enforce the duplicate-alias-is-fatal rule.
43    pub fn bind(&mut self, name: &str, alias: Option<u64>, datatype: DataType) {
44        self.dt_by_name.insert(name.to_owned(), datatype);
45        if let Some(alias) = alias {
46            self.name_to_alias.insert(name.to_owned(), alias);
47            self.alias_to_name.insert(alias, name.to_owned());
48            self.dt_by_alias.insert(alias, datatype);
49        }
50    }
51
52    /// Like [`AliasRegistry::bind`], but rejects an alias already bound to a
53    /// different name — a duplicate alias in a BIRTH is fatal per the spec.
54    ///
55    /// # Errors
56    /// Returns [`SparkplugError::InvalidId`] if `alias` is already bound to a
57    /// different metric name.
58    pub fn try_bind(&mut self, name: &str, alias: Option<u64>, datatype: DataType) -> Result<()> {
59        if let Some(alias) = alias
60            && let Some(existing) = self.alias_to_name.get(&alias)
61            && existing != name
62        {
63            return Err(SparkplugError::InvalidId(format!(
64                "alias {alias} already bound to {existing:?}, cannot rebind to {name:?}"
65            )));
66        }
67        self.bind(name, alias, datatype);
68        Ok(())
69    }
70
71    /// The datatype declared for `name`, if any.
72    #[must_use]
73    pub fn datatype_for_name(&self, name: &str) -> Option<DataType> {
74        self.dt_by_name.get(name).copied()
75    }
76
77    /// The datatype declared for `alias`, if any.
78    #[must_use]
79    pub fn datatype_for_alias(&self, alias: u64) -> Option<DataType> {
80        self.dt_by_alias.get(&alias).copied()
81    }
82
83    /// The datatype for a [`MetricKey`].
84    #[must_use]
85    pub fn datatype_for(&self, key: &MetricKey) -> Option<DataType> {
86        match key {
87            MetricKey::Name(n) => self.datatype_for_name(n),
88            MetricKey::Alias(a) => self.datatype_for_alias(*a),
89        }
90    }
91
92    /// The name bound to `alias`, if any.
93    #[must_use]
94    pub fn name_for_alias(&self, alias: u64) -> Option<&str> {
95        self.alias_to_name.get(&alias).map(String::as_str)
96    }
97
98    /// The alias bound to `name`, if any.
99    #[must_use]
100    pub fn alias_for_name(&self, name: &str) -> Option<u64> {
101        self.name_to_alias.get(name).copied()
102    }
103
104    /// Whether `alias` is already bound (to detect duplicate aliases in a birth).
105    #[must_use]
106    pub fn alias_exists(&self, alias: u64) -> bool {
107        self.alias_to_name.contains_key(&alias)
108    }
109
110    /// Clear every binding (call before re-binding on a new birth).
111    pub fn clear(&mut self) {
112        self.name_to_alias.clear();
113        self.alias_to_name.clear();
114        self.dt_by_name.clear();
115        self.dt_by_alias.clear();
116    }
117}