use alloc::string::String;
use core::{
fmt::{self, Display},
ops::Deref,
};
use unicase::UniCase;
use bytestring::ByteString;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct HeaderName(UniCase<ByteString>);
impl HeaderName {
pub const MESSAGE_ID: Self = Self::new_internal("Nats-Msg-Id");
pub const EXPECTED_STREAM: Self = Self::new_internal("Nats-Expected-Stream");
pub const EXPECTED_LAST_MESSAGE_ID: Self = Self::new_internal("Nats-Expected-Last-Msg-Id");
pub const EXPECTED_LAST_SEQUENCE: Self = Self::new_internal("Nats-Expected-Last-Sequence");
pub const ROLLUP: Self = Self::new_internal("Nats-Rollup");
pub const STREAM: Self = Self::new_internal("Nats-Stream");
pub const SUBJECT: Self = Self::new_internal("Nats-Subject");
pub const SEQUENCE: Self = Self::new_internal("Nats-Sequence");
pub const LAST_SEQUENCE: Self = Self::new_internal("Nats-Last-Sequence");
pub const TIMESTAMP: Self = Self::new_internal("Nats-Time-Stamp");
pub const STREAM_SOURCE: Self = Self::new_internal("Nats-Stream-Source");
pub const MESSAGE_SIZE: Self = Self::new_internal("Nats-Msg-Size");
#[must_use]
pub fn from_static(value: &'static str) -> Self {
Self::try_from(ByteString::from_static(value)).expect("invalid HeaderName")
}
#[expect(
clippy::missing_panics_doc,
reason = "The header validation is only made in debug"
)]
#[must_use]
pub fn from_dangerous_value(value: ByteString) -> Self {
if cfg!(debug_assertions) {
if let Err(err) = validate_header_name(&value) {
panic!("HeaderName {value:?} isn't valid {err:?}");
}
}
Self(UniCase::new(value))
}
const fn new_internal(value: &'static str) -> Self {
if value.is_ascii() {
Self(UniCase::ascii(ByteString::from_static(value)))
} else {
Self(UniCase::unicode(ByteString::from_static(value)))
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for HeaderName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl TryFrom<ByteString> for HeaderName {
type Error = HeaderNameValidateError;
fn try_from(value: ByteString) -> Result<Self, Self::Error> {
validate_header_name(&value)?;
Ok(Self::from_dangerous_value(value))
}
}
impl TryFrom<String> for HeaderName {
type Error = HeaderNameValidateError;
fn try_from(value: String) -> Result<Self, Self::Error> {
validate_header_name(&value)?;
Ok(Self::from_dangerous_value(value.into()))
}
}
impl From<HeaderName> for ByteString {
fn from(value: HeaderName) -> Self {
value.0.into_inner()
}
}
impl AsRef<[u8]> for HeaderName {
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl AsRef<str> for HeaderName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for HeaderName {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
#[derive(Debug, thiserror::Error)]
pub enum HeaderNameValidateError {
#[error("HeaderName is empty")]
Empty,
#[error("HeaderName is too long")]
TooLong,
#[error("HeaderName contained an illegal whitespace character")]
IllegalCharacter,
}
fn validate_header_name(header_name: &str) -> Result<(), HeaderNameValidateError> {
if header_name.is_empty() {
return Err(HeaderNameValidateError::Empty);
}
if header_name.len() > 64 {
return Err(HeaderNameValidateError::TooLong);
}
if header_name.chars().any(|c| c.is_whitespace() || c == ':') {
return Err(HeaderNameValidateError::IllegalCharacter);
}
Ok(())
}
#[cfg(test)]
mod tests {
use core::cmp::Ordering;
use super::HeaderName;
#[test]
fn eq() {
let cased = HeaderName::from_static("Nats-Message-Id");
let lowercase = HeaderName::from_static("nats-message-id");
assert_eq!(cased, lowercase);
assert_eq!(cased.cmp(&lowercase), Ordering::Equal);
}
}