use std::error::Error;
use std::fmt;
pub const MAX_STABLE_MESSAGE_ID_LEN: usize = 512;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum StableMessageIdError {
Missing,
Empty,
TooLong {
len: usize,
},
InvalidCharacter,
}
impl fmt::Display for StableMessageIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StableMessageIdError::Missing => {
write!(f, "stable message id is required but missing")
}
StableMessageIdError::Empty => {
write!(f, "stable message id is empty")
}
StableMessageIdError::TooLong { len } => write!(
f,
"stable message id is {len} bytes, exceeding the maximum of {MAX_STABLE_MESSAGE_ID_LEN}"
),
StableMessageIdError::InvalidCharacter => {
write!(f, "stable message id contains a control character")
}
}
}
}
impl Error for StableMessageIdError {}
pub fn validate_stable_message_id(id: Option<&str>) -> Result<&str, StableMessageIdError> {
let id = id.ok_or(StableMessageIdError::Missing)?;
if id.trim().is_empty() {
return Err(StableMessageIdError::Empty);
}
if id.len() > MAX_STABLE_MESSAGE_ID_LEN {
return Err(StableMessageIdError::TooLong { len: id.len() });
}
if id.chars().any(|c| c.is_control()) {
return Err(StableMessageIdError::InvalidCharacter);
}
Ok(id)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_a_well_formed_id() {
assert_eq!(validate_stable_message_id(Some("evt-1")), Ok("evt-1"));
assert_eq!(validate_stable_message_id(Some(" evt-1 ")), Ok(" evt-1 "));
}
#[test]
fn rejects_missing_id() {
assert_eq!(
validate_stable_message_id(None),
Err(StableMessageIdError::Missing)
);
}
#[test]
fn rejects_empty_or_whitespace_id() {
assert_eq!(
validate_stable_message_id(Some("")),
Err(StableMessageIdError::Empty)
);
assert_eq!(
validate_stable_message_id(Some(" ")),
Err(StableMessageIdError::Empty)
);
}
#[test]
fn rejects_over_long_id() {
let id = "a".repeat(MAX_STABLE_MESSAGE_ID_LEN + 1);
assert_eq!(
validate_stable_message_id(Some(&id)),
Err(StableMessageIdError::TooLong {
len: MAX_STABLE_MESSAGE_ID_LEN + 1
})
);
let boundary = "a".repeat(MAX_STABLE_MESSAGE_ID_LEN);
assert!(validate_stable_message_id(Some(&boundary)).is_ok());
}
#[test]
fn rejects_control_characters() {
assert_eq!(
validate_stable_message_id(Some("evt\n1")),
Err(StableMessageIdError::InvalidCharacter)
);
assert_eq!(
validate_stable_message_id(Some("evt\u{0}1")),
Err(StableMessageIdError::InvalidCharacter)
);
assert_eq!(
validate_stable_message_id(Some("evt\t1")),
Err(StableMessageIdError::InvalidCharacter)
);
}
}