use std::error::Error;
use std::fmt;
pub const MAX_MESSAGE_NAME_LEN: usize = 256;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum MessageNameError {
Empty,
TooLong {
len: usize,
},
ControlCharacter,
Wildcard {
ch: char,
},
}
impl fmt::Display for MessageNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MessageNameError::Empty => write!(f, "message name is empty"),
MessageNameError::TooLong { len } => write!(
f,
"message name is {len} bytes, exceeding the maximum of {MAX_MESSAGE_NAME_LEN}"
),
MessageNameError::ControlCharacter => {
write!(f, "message name contains a control character")
}
MessageNameError::Wildcard { ch } => write!(
f,
"message name contains broker wildcard `{ch}`; \
`*`, `#`, and `>` are reserved routing operators"
),
}
}
}
impl Error for MessageNameError {}
pub fn validate_message_name(name: &str) -> Result<&str, MessageNameError> {
if name.trim().is_empty() {
return Err(MessageNameError::Empty);
}
if name.len() > MAX_MESSAGE_NAME_LEN {
return Err(MessageNameError::TooLong { len: name.len() });
}
for ch in name.chars() {
if ch.is_control() {
return Err(MessageNameError::ControlCharacter);
}
if matches!(ch, '*' | '#' | '>') {
return Err(MessageNameError::Wildcard { ch });
}
}
Ok(name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_dotted_type_names() {
assert_eq!(validate_message_name("order.created"), Ok("order.created"));
assert_eq!(
validate_message_name("counter.incremented"),
Ok("counter.incremented")
);
assert_eq!(validate_message_name("work"), Ok("work"));
}
#[test]
fn rejects_empty_or_whitespace() {
assert_eq!(validate_message_name(""), Err(MessageNameError::Empty));
assert_eq!(validate_message_name(" "), Err(MessageNameError::Empty));
}
#[test]
fn rejects_over_long_names() {
let name = "a".repeat(MAX_MESSAGE_NAME_LEN + 1);
assert_eq!(
validate_message_name(&name),
Err(MessageNameError::TooLong {
len: MAX_MESSAGE_NAME_LEN + 1
})
);
let boundary = "a".repeat(MAX_MESSAGE_NAME_LEN);
assert!(validate_message_name(&boundary).is_ok());
}
#[test]
fn rejects_control_characters() {
assert_eq!(
validate_message_name("order\ncreated"),
Err(MessageNameError::ControlCharacter)
);
assert_eq!(
validate_message_name("order\u{0}created"),
Err(MessageNameError::ControlCharacter)
);
}
#[test]
fn rejects_broker_wildcards() {
assert_eq!(
validate_message_name("order.*"),
Err(MessageNameError::Wildcard { ch: '*' })
);
assert_eq!(
validate_message_name("order.#"),
Err(MessageNameError::Wildcard { ch: '#' })
);
assert_eq!(
validate_message_name("orders>"),
Err(MessageNameError::Wildcard { ch: '>' })
);
}
}