use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WorkloadId(u8);
impl WorkloadId {
pub const MIN: u8 = 1;
pub const MAX: u8 = 50;
pub fn new(value: u8) -> Self {
assert!(
(Self::MIN..=Self::MAX).contains(&value),
"WorkloadId must be between {} and {} (inclusive), got {}",
Self::MIN,
Self::MAX,
value
);
Self(value)
}
pub fn try_new(value: u8) -> Option<Self> {
if (Self::MIN..=Self::MAX).contains(&value) {
Some(Self(value))
} else {
None
}
}
pub fn value(&self) -> u8 {
self.0
}
}
impl TryFrom<u8> for WorkloadId {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::try_new(value).ok_or("WorkloadId must be between 1 and 50 (inclusive)")
}
}
impl fmt::Display for WorkloadId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
fn is_http_header_safe(s: &str) -> bool {
s.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.' | '~'))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CorrelationId(String);
impl CorrelationId {
pub const MAX_LENGTH: usize = 50;
pub fn new(value: impl Into<String>) -> Self {
let value = value.into();
assert!(
value.len() <= Self::MAX_LENGTH,
"CorrelationId must be at most {} characters, got {}",
Self::MAX_LENGTH,
value.len()
);
assert!(
is_http_header_safe(&value),
"CorrelationId must contain only HTTP header-safe characters (alphanumeric, hyphen, underscore, dot, tilde)"
);
Self(value)
}
pub fn try_new(value: impl Into<String>) -> Option<Self> {
let value = value.into();
if value.len() <= Self::MAX_LENGTH && is_http_header_safe(&value) {
Some(Self(value))
} else {
None
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for CorrelationId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for CorrelationId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UserAgentSuffix(String);
impl UserAgentSuffix {
pub const MAX_LENGTH: usize = 25;
pub fn new(value: impl Into<String>) -> Self {
let value = value.into();
assert!(
value.len() <= Self::MAX_LENGTH,
"UserAgentSuffix must be at most {} characters, got {}",
Self::MAX_LENGTH,
value.len()
);
assert!(
is_http_header_safe(&value),
"UserAgentSuffix must contain only HTTP header-safe characters (alphanumeric, hyphen, underscore, dot, tilde)"
);
Self(value)
}
pub fn try_new(value: impl Into<String>) -> Option<Self> {
let value = value.into();
if value.len() <= Self::MAX_LENGTH && is_http_header_safe(&value) {
Some(Self(value))
} else {
None
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for UserAgentSuffix {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for UserAgentSuffix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn workload_id_valid_range() {
assert!(WorkloadId::try_new(1).is_some());
assert!(WorkloadId::try_new(25).is_some());
assert!(WorkloadId::try_new(50).is_some());
}
#[test]
fn workload_id_invalid_range() {
assert!(WorkloadId::try_new(0).is_none());
assert!(WorkloadId::try_new(51).is_none());
assert!(WorkloadId::try_new(255).is_none());
}
#[test]
#[should_panic(expected = "WorkloadId must be between 1 and 50")]
fn workload_id_panics_on_zero() {
WorkloadId::new(0);
}
#[test]
fn correlation_id_valid() {
let id = CorrelationId::new("aks-prod-eastus-001");
assert_eq!(id.as_str(), "aks-prod-eastus-001");
}
#[test]
fn correlation_id_max_length() {
let long_id = "a".repeat(50);
assert!(CorrelationId::try_new(&long_id).is_some());
let too_long = "a".repeat(51);
assert!(CorrelationId::try_new(&too_long).is_none());
}
#[test]
fn correlation_id_invalid_chars() {
assert!(CorrelationId::try_new("valid-id_123").is_some());
assert!(CorrelationId::try_new("invalid id").is_none()); assert!(CorrelationId::try_new("invalid/id").is_none()); assert!(CorrelationId::try_new("invalid:id").is_none()); }
#[test]
fn user_agent_suffix_valid() {
let suffix = UserAgentSuffix::new("myapp-westus2");
assert_eq!(suffix.as_str(), "myapp-westus2");
}
#[test]
fn user_agent_suffix_max_length() {
let long_suffix = "a".repeat(25);
assert!(UserAgentSuffix::try_new(&long_suffix).is_some());
let too_long = "a".repeat(26);
assert!(UserAgentSuffix::try_new(&too_long).is_none());
}
#[test]
fn user_agent_suffix_invalid_chars() {
assert!(UserAgentSuffix::try_new("valid-suffix").is_some());
assert!(UserAgentSuffix::try_new("invalid suffix").is_none()); }
}