use std::collections::HashMap;
use super::error::SubnetError;
use super::id::SubnetId;
use crate::adapter::net::behavior::capability::CapabilitySet;
#[derive(Debug, Clone)]
pub struct SubnetPolicy {
rules: Vec<SubnetRule>,
}
#[derive(Debug, Clone)]
pub struct SubnetRule {
pub tag_prefix: String,
pub level: u8,
pub values: HashMap<String, u8>,
}
impl SubnetPolicy {
pub fn new() -> Self {
Self { rules: Vec::new() }
}
#[expect(
clippy::expect_used,
reason = "documented panicking variant; try_add_rule is the fallible alternative for untrusted input"
)]
pub fn add_rule(self, rule: SubnetRule) -> Self {
self.try_add_rule(rule)
.expect("SubnetPolicy::add_rule: invalid rule (use try_add_rule for fallible)")
}
pub fn try_add_rule(mut self, rule: SubnetRule) -> Result<Self, SubnetError> {
if rule.level >= 4 {
return Err(SubnetError::LevelOutOfRange { got: rule.level });
}
self.rules.push(rule);
Ok(self)
}
pub fn assign(&self, caps: &CapabilitySet) -> SubnetId {
let mut levels = [0u8; 4];
let mut tag_strings: Vec<String> = caps.tags.iter().map(|t| t.to_string()).collect();
tag_strings.sort();
for rule in &self.rules {
for s in &tag_strings {
if let Some(value) = s.strip_prefix(&rule.tag_prefix) {
if let Some(&level_value) = rule.values.get(value) {
levels[rule.level as usize] = level_value;
break; }
}
}
}
SubnetId::new(&levels)
}
}
impl Default for SubnetPolicy {
fn default() -> Self {
Self::new()
}
}
impl SubnetRule {
pub fn new(tag_prefix: impl Into<String>, level: u8) -> Self {
Self {
tag_prefix: tag_prefix.into(),
level,
values: HashMap::new(),
}
}
#[expect(
clippy::expect_used,
reason = "documented panicking variant; try_map is the fallible alternative for untrusted input"
)]
pub fn map(self, tag_value: impl Into<String>, level_value: u8) -> Self {
self.try_map(tag_value, level_value)
.expect("SubnetRule::map: level_value 0 is reserved (use try_map for fallible)")
}
pub fn try_map(
mut self,
tag_value: impl Into<String>,
level_value: u8,
) -> Result<Self, SubnetError> {
if level_value == 0 {
return Err(SubnetError::LevelValueReserved);
}
self.values.insert(tag_value.into(), level_value);
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapter::net::behavior::capability::CapabilitySet;
fn caps_with_tags(tags: &[&str]) -> CapabilitySet {
let mut caps = CapabilitySet::new();
for tag in tags {
caps = caps.add_tag(*tag);
}
caps
}
#[test]
fn test_empty_policy() {
let policy = SubnetPolicy::new();
let caps = caps_with_tags(&["region:us-west"]);
assert_eq!(policy.assign(&caps), SubnetId::GLOBAL);
}
#[test]
fn test_single_level() {
let policy = SubnetPolicy::new().add_rule(
SubnetRule::new("region:", 0)
.map("us-west", 1)
.map("eu-central", 2),
);
let caps = caps_with_tags(&["region:us-west"]);
assert_eq!(policy.assign(&caps), SubnetId::new(&[1]));
let caps = caps_with_tags(&["region:eu-central"]);
assert_eq!(policy.assign(&caps), SubnetId::new(&[2]));
}
#[test]
fn test_multi_level() {
let policy = SubnetPolicy::new()
.add_rule(
SubnetRule::new("region:", 0)
.map("us-west", 1)
.map("eu-central", 2),
)
.add_rule(SubnetRule::new("fleet:", 1).map("alpha", 1).map("beta", 2));
let caps = caps_with_tags(&["region:us-west", "fleet:beta"]);
assert_eq!(policy.assign(&caps), SubnetId::new(&[1, 2]));
}
#[test]
fn test_unmatched_tag() {
let policy = SubnetPolicy::new().add_rule(SubnetRule::new("region:", 0).map("us-west", 1));
let caps = caps_with_tags(&["region:unknown"]);
assert_eq!(policy.assign(&caps), SubnetId::GLOBAL);
let caps = caps_with_tags(&["fleet:alpha"]);
assert_eq!(policy.assign(&caps), SubnetId::GLOBAL);
}
#[test]
fn test_partial_match() {
let policy = SubnetPolicy::new()
.add_rule(SubnetRule::new("region:", 0).map("us-west", 3))
.add_rule(SubnetRule::new("fleet:", 1).map("alpha", 7));
let caps = caps_with_tags(&["region:us-west"]);
assert_eq!(policy.assign(&caps), SubnetId::new(&[3]));
}
#[test]
fn test_four_levels() {
let policy = SubnetPolicy::new()
.add_rule(SubnetRule::new("region:", 0).map("us", 1))
.add_rule(SubnetRule::new("fleet:", 1).map("f1", 2))
.add_rule(SubnetRule::new("vehicle:", 2).map("v42", 3))
.add_rule(SubnetRule::new("subsystem:", 3).map("lidar", 4));
let caps = caps_with_tags(&["region:us", "fleet:f1", "vehicle:v42", "subsystem:lidar"]);
assert_eq!(policy.assign(&caps), SubnetId::new(&[1, 2, 3, 4]));
}
#[test]
fn duplicate_prefix_same_level_later_rule_wins() {
let policy = SubnetPolicy::new()
.add_rule(SubnetRule::new("region:", 0).map("us", 1))
.add_rule(SubnetRule::new("region:", 0).map("us", 9));
let caps = caps_with_tags(&["region:us"]);
assert_eq!(
policy.assign(&caps),
SubnetId::new(&[9]),
"a later rule with the same prefix + level must overwrite \
the earlier rule's value — pinned as last-write-wins",
);
}
#[test]
fn duplicate_prefix_different_levels_both_apply() {
let policy = SubnetPolicy::new()
.add_rule(SubnetRule::new("region:", 0).map("us", 1))
.add_rule(SubnetRule::new("region:", 2).map("us", 5));
let caps = caps_with_tags(&["region:us"]);
assert_eq!(
policy.assign(&caps),
SubnetId::new(&[1, 0, 5, 0]),
"two rules sharing a prefix but targeting different \
levels must both fire; level 1 + 3 remain unset",
);
}
#[test]
fn rule_order_dependency_later_rule_overwrites_earlier_level_write() {
let policy = SubnetPolicy::new()
.add_rule(SubnetRule::new("region:", 0).map("us", 1))
.add_rule(SubnetRule::new("zone:", 0).map("west", 4));
let caps = caps_with_tags(&["region:us", "zone:west"]);
assert_eq!(
policy.assign(&caps),
SubnetId::new(&[4]),
"later rule targeting the same level must overwrite earlier one",
);
let caps = caps_with_tags(&["region:us"]);
assert_eq!(
policy.assign(&caps),
SubnetId::new(&[1]),
"later rule does not clobber when it has no matching tag",
);
}
#[test]
fn partial_prefix_on_value_does_not_match() {
let policy = SubnetPolicy::new().add_rule(SubnetRule::new("region:", 0).map("us", 1));
let caps = caps_with_tags(&["region:us"]);
assert_eq!(policy.assign(&caps), SubnetId::new(&[1]));
let caps = caps_with_tags(&["region:us:extra"]);
assert_eq!(
policy.assign(&caps),
SubnetId::GLOBAL,
"values map is exact-match; suffixes after the matching \
inner token must not partial-match against the map key",
);
let policy = SubnetPolicy::new().add_rule(SubnetRule::new("region:", 0).map("us-west", 1));
let caps = caps_with_tags(&["region:us"]);
assert_eq!(
policy.assign(&caps),
SubnetId::GLOBAL,
"stripped value \"us\" is a prefix of \"us-west\" but \
must not partial-match the values map key",
);
}
#[test]
fn first_tag_wins_within_a_single_rule() {
let policy =
SubnetPolicy::new().add_rule(SubnetRule::new("region:", 0).map("us", 1).map("eu", 2));
let caps = caps_with_tags(&["region:us", "region:eu"]);
assert_eq!(
policy.assign(&caps),
SubnetId::new(&[2]),
"lexicographically-first matching tag wins (`region:eu` < `region:us`)",
);
let caps = caps_with_tags(&["region:eu", "region:us"]);
assert_eq!(
policy.assign(&caps),
SubnetId::new(&[2]),
"insertion order is irrelevant — the same tag still wins",
);
}
#[test]
fn try_add_rule_rejects_level_out_of_range() {
let policy = SubnetPolicy::new();
let err = policy
.try_add_rule(SubnetRule::new("region:", 4).map("us", 1))
.unwrap_err();
assert!(
matches!(err, SubnetError::LevelOutOfRange { got: 4 }),
"expected LevelOutOfRange{{got: 4}}, got {:?}",
err
);
}
#[test]
fn try_add_rule_accepts_max_level() {
let policy = SubnetPolicy::new();
policy
.try_add_rule(SubnetRule::new("level3:", 3).map("x", 1))
.expect("level=3 must be accepted (boundary)");
}
#[test]
fn try_map_rejects_reserved_zero() {
let rule = SubnetRule::new("region:", 0);
let err = rule.try_map("us", 0).unwrap_err();
assert!(
matches!(err, SubnetError::LevelValueReserved),
"expected LevelValueReserved, got {:?}",
err
);
}
#[test]
fn try_map_accepts_one() {
SubnetRule::new("region:", 0)
.try_map("us", 1)
.expect("level_value=1 must be accepted (boundary)");
}
}