refactor brocade to support different shell versions (e.g. FastIron vs NOS)
All checks were successful
Run Check Script / check (pull_request) Successful in 1m13s

This commit is contained in:
Ian Letourneau
2025-10-07 21:27:45 -04:00
parent 45e0de2097
commit 77e09436a9
13 changed files with 973 additions and 634 deletions

View File

@@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize};
///
/// **It is not meant to be very secure or unique**, it is suitable to generate up to 10 000 items per
/// second with a reasonable collision rate of 0,000014 % as calculated by this calculator : https://kevingal.com/apps/collision.html
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Id {
value: String,
}

View File

@@ -1,2 +1,3 @@
pub mod id;
pub mod net;
pub mod switch;

176
harmony_types/src/switch.rs Normal file
View File

@@ -0,0 +1,176 @@
use std::{fmt, str::FromStr};
/// Simple error type for port parsing failures.
#[derive(Debug)]
pub enum PortParseError {
/// The port string did not conform to the expected S/M/P or range format.
InvalidFormat,
/// A stack, module, or port segment could not be parsed as a number.
InvalidSegment(String),
}
impl fmt::Display for PortParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PortParseError::InvalidFormat => write!(f, "Port string is in an unexpected format."),
PortParseError::InvalidSegment(s) => write!(f, "Invalid segment in port string: {}", s),
}
}
}
/// Represents the atomic, physical location of a switch port: `<Stack>/<Module>/<Port>`.
///
/// Example: `1/1/1`
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct PortLocation(pub u8, pub u8, pub u8);
impl fmt::Display for PortLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}/{}", self.0, self.1, self.2)
}
}
impl FromStr for PortLocation {
type Err = PortParseError;
/// Parses a string slice into a `PortLocation`.
///
/// # Examples
///
/// ```rust
/// use std::str::FromStr;
/// use harmony_types::switch::PortLocation;
///
/// assert_eq!(PortLocation::from_str("1/1/1").unwrap(), PortLocation(1, 1, 1));
/// assert_eq!(PortLocation::from_str("12/5/48").unwrap(), PortLocation(12, 5, 48));
/// assert!(PortLocation::from_str("1/A/1").is_err());
/// ```
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split('/').collect();
if parts.len() != 3 {
return Err(PortParseError::InvalidFormat);
}
let parse_segment = |part: &str| -> Result<u8, Self::Err> {
u8::from_str(part).map_err(|_| PortParseError::InvalidSegment(part.to_string()))
};
let stack = parse_segment(parts[0])?;
let module = parse_segment(parts[1])?;
let port = parse_segment(parts[2])?;
Ok(PortLocation(stack, module, port))
}
}
/// Represents a Port configuration input, which can be a single port, a sequential range,
/// or an explicit set defined by endpoints.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum PortDeclaration {
/// A single switch port defined by its location. Example: `PortDeclaration::Single(1/1/1)`
Single(PortLocation),
/// A strictly sequential range defined by two endpoints using the hyphen separator (`-`).
/// All ports between the endpoints (inclusive) are implicitly included.
/// Example: `PortDeclaration::Range(1/1/1, 1/1/4)`
Range(PortLocation, PortLocation),
/// A set of ports defined by two endpoints using the asterisk separator (`*`).
/// The actual member ports must be determined contextually (e.g., from MAC tables or
/// explicit configuration lists).
/// Example: `PortDeclaration::Set(1/1/1, 1/1/3)` where only ports 1 and 3 might be active.
Set(PortLocation, PortLocation),
}
impl PortDeclaration {
/// Parses a port configuration string into a structured `PortDeclaration` enum.
///
/// This function performs only basic format and numerical parsing, assuming the input
/// strings (e.g., from `show` commands) are semantically valid and logically ordered.
///
/// # Supported Formats
///
/// * **Single Port:** `"1/1/1"`
/// * **Range (Hyphen, `-`):** `"1/1/1-1/1/4"`
/// * **Set (Asterisk, `*`):** `"1/1/1*1/1/4"`
///
/// # Errors
///
/// Returns `PortParseError` if the string format is incorrect or numerical segments
/// cannot be parsed.
///
/// # Examples
///
/// ```rust
/// use harmony_types::switch::{PortDeclaration, PortLocation};
///
/// // Single Port
/// assert_eq!(PortDeclaration::parse("3/2/15").unwrap(), PortDeclaration::Single(PortLocation(3, 2, 15)));
///
/// // Range (Hyphen) - implies sequential ports
/// let result_range = PortDeclaration::parse("1/1/1-1/1/4").unwrap();
/// assert_eq!(result_range, PortDeclaration::Range(PortLocation(1, 1, 1), PortLocation(1, 1, 4)));
///
/// // Set (Asterisk) - implies non-sequential set defined by endpoints
/// let result_set = PortDeclaration::parse("1/1/48*2/1/48").unwrap();
/// assert_eq!(result_set, PortDeclaration::Set(PortLocation(1, 1, 48), PortLocation(2, 1, 48)));
///
/// // Invalid Format (will still fail basic parsing)
/// assert!(PortDeclaration::parse("1/1/1/1").is_err());
/// ```
pub fn parse(port_str: &str) -> Result<Self, PortParseError> {
if let Some((start_str, end_str)) = port_str.split_once('-') {
let start_port = PortLocation::from_str(start_str.trim())?;
let end_port = PortLocation::from_str(end_str.trim())?;
return Ok(PortDeclaration::Range(start_port, end_port));
}
if let Some((start_str, end_str)) = port_str.split_once('*') {
let start_port = PortLocation::from_str(start_str.trim())?;
let end_port = PortLocation::from_str(end_str.trim())?;
return Ok(PortDeclaration::Set(start_port, end_port));
}
let location = PortLocation::from_str(port_str)?;
Ok(PortDeclaration::Single(location))
}
}
impl fmt::Display for PortDeclaration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PortDeclaration::Single(port) => write!(f, "{port}"),
PortDeclaration::Range(start, end) => write!(f, "{start}-{end}"),
PortDeclaration::Set(start, end) => write!(f, "{start}*{end}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_port_location_invalid() {
assert!(PortLocation::from_str("1/1").is_err());
assert!(PortLocation::from_str("1/A/1").is_err());
assert!(PortLocation::from_str("1/1/256").is_err());
}
#[test]
fn test_parse_declaration_single() {
let single_result = PortDeclaration::parse("1/1/4").unwrap();
assert!(matches!(single_result, PortDeclaration::Single(_)));
}
#[test]
fn test_parse_declaration_range() {
let range_result = PortDeclaration::parse("1/1/1-1/1/4").unwrap();
assert!(matches!(range_result, PortDeclaration::Range(_, _)));
}
#[test]
fn test_parse_declaration_set() {
let set_result = PortDeclaration::parse("1/1/48*2/1/48").unwrap();
assert!(matches!(set_result, PortDeclaration::Set(_, _)));
}
}