This adds a new type to reference aliases with proper scope handling (Datacenter, Guest, or None for legacy aliases).
The implementation includes: - FirewallAliasScope enum for scope variants - FirewallAliasReference struct with validation - Proper encapsulation with constructor and accessor methods - FromStr implementation for parsing alias references Signed-off-by: Dietmar Maurer <[email protected]> --- proxmox-firewall-api-types/src/alias.rs | 142 ++++++++++++++++++++++++ proxmox-firewall-api-types/src/lib.rs | 3 + 2 files changed, 145 insertions(+) create mode 100644 proxmox-firewall-api-types/src/alias.rs diff --git a/proxmox-firewall-api-types/src/alias.rs b/proxmox-firewall-api-types/src/alias.rs new file mode 100644 index 00000000..7722148c --- /dev/null +++ b/proxmox-firewall-api-types/src/alias.rs @@ -0,0 +1,142 @@ +use std::fmt; +use std::str::FromStr; + +use anyhow::{bail, Error}; + +#[cfg(feature = "enum-fallback")] +use proxmox_fixed_string::FixedString; + +/// The scope of an alias. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum FirewallAliasScope { + /// Datacenter scope. + Datacenter, + /// Guest scope. + Guest, + /// No scope (e.g. for legacy aliases). + None, + #[cfg(feature = "enum-fallback")] + /// Unknown variants for forward compatibility. + UnknownEnumValue(FixedString), +} + +/// A reference to an alias, including its scope. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FirewallAliasReference { + scope: FirewallAliasScope, + name: String, +} + +impl FirewallAliasReference { + pub fn new(scope: FirewallAliasScope, name: String) -> Result<Self, Error> { + verify_alias_name(&name)?; + Ok(Self { scope, name }) + } + + pub fn scope(&self) -> FirewallAliasScope { + self.scope + } + + pub fn name(&self) -> &str { + &self.name + } +} + +fn verify_alias_name(name: &str) -> Result<(), Error> { + if name.is_empty() { + bail!("alias name cannot be empty"); + } + + if !name.starts_with(|c: char| c.is_ascii_alphabetic()) { + bail!("alias name must start with an ASCII letter"); + } + + if name.contains(|c: char| !c.is_ascii_alphanumeric() && c != '-' && c != '_') { + bail!("alias name can only contain ASCII letters, digits, hyphens, and underscores"); + } + + Ok(()) +} + +impl fmt::Display for FirewallAliasReference { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.scope { + FirewallAliasScope::Datacenter => write!(f, "dc/{}", self.name), + FirewallAliasScope::Guest => write!(f, "guest/{}", self.name), + #[cfg(feature = "enum-fallback")] + FirewallAliasScope::UnknownEnumValue(scope) => write!(f, "{}/{}", scope, self.name), + FirewallAliasScope::None => self.name.fmt(f), + } + } +} + +impl FromStr for FirewallAliasReference { + type Err = Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let s = s.trim(); + + if s.is_empty() { + bail!("empty firewall alias specification"); + } + + let (scope, name) = match s.split_once('/') { + Some(("dc", alias)) => (FirewallAliasScope::Datacenter, alias), + Some(("guest", alias)) => (FirewallAliasScope::Guest, alias), + #[cfg(not(feature = "enum-fallback"))] + Some((scope, _alias)) => bail!("invalid firewall alias scope: {scope}"), + #[cfg(feature = "enum-fallback")] + Some((scope, alias)) => ( + FirewallAliasScope::UnknownEnumValue(FixedString::from_str(scope)?), + alias, + ), + None => (FirewallAliasScope::None, s), + }; + + Self::new(scope, name.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_legacy_alias_name() { + for name in ["proxmox_123", "proxmox-123"] { + name.parse::<FirewallAliasReference>() + .expect("valid alias name"); + } + + for name in ["1proxmox_123", "-proxmox-123"] { + name.parse::<FirewallAliasReference>() + .expect_err("invalid alias name"); + } + } + + #[test] + fn test_new_alias() { + let alias = + FirewallAliasReference::new(FirewallAliasScope::Datacenter, "proxmox-123".to_string()) + .expect("valid alias name"); + assert_eq!(alias.scope(), FirewallAliasScope::Datacenter); + assert_eq!(alias.name(), "proxmox-123"); + + FirewallAliasReference::new(FirewallAliasScope::Datacenter, "invalid/name".to_string()) + .expect_err("invalid alias name"); + } + + #[test] + fn test_parse_alias_name() { + for name in ["dc/proxmox_123", "guest/proxmox-123"] { + name.parse::<FirewallAliasReference>() + .expect("valid alias name"); + } + + #[cfg(not(feature = "enum-fallback"))] + for name in ["proxmox/proxmox_123", "guests/proxmox-123", "dc/", "/name"] { + name.parse::<FirewallAliasReference>() + .expect_err("invalid alias name"); + } + } +} diff --git a/proxmox-firewall-api-types/src/lib.rs b/proxmox-firewall-api-types/src/lib.rs index abd78c98..8fae5042 100644 --- a/proxmox-firewall-api-types/src/lib.rs +++ b/proxmox-firewall-api-types/src/lib.rs @@ -1,3 +1,6 @@ +mod alias; +pub use alias::{FirewallAliasReference, FirewallAliasScope}; + mod conntrack; pub use conntrack::FirewallConntrackHelper; -- 2.47.3
