use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VaryError {
Empty,
InvalidFormat,
InvalidFieldName,
}
impl fmt::Display for VaryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VaryError::Empty => write!(f, "empty Vary header"),
VaryError::InvalidFormat => write!(f, "invalid Vary header format"),
VaryError::InvalidFieldName => write!(f, "invalid Vary header field name"),
}
}
}
impl std::error::Error for VaryError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Vary {
any: bool,
fields: Vec<String>,
}
impl Vary {
pub fn parse(input: &str) -> Result<Self, VaryError> {
let input = input.trim();
let mut any = false;
let mut fields = Vec::new();
for part in input.split(',') {
let name = part.trim();
if name.is_empty() {
continue;
}
if name == "*" {
any = true;
} else {
if !is_valid_token(name) {
return Err(VaryError::InvalidFieldName);
}
fields.push(name.to_ascii_lowercase());
}
}
if any {
fields.clear();
}
Ok(Vary { any, fields })
}
pub fn is_any(&self) -> bool {
self.any
}
pub fn fields(&self) -> &[String] {
&self.fields
}
}
impl fmt::Display for Vary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.any {
return write!(f, "*");
}
write!(f, "{}", self.fields.join(", "))
}
}
fn is_valid_token(s: &str) -> bool {
!s.is_empty() && s.bytes().all(is_token_char)
}
fn is_token_char(b: u8) -> bool {
matches!(
b,
b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+' | b'-' | b'.' |
b'0'..=b'9' | b'A'..=b'Z' | b'^' | b'_' | b'`' | b'a'..=b'z' | b'|' | b'~'
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_any() {
let vary = Vary::parse("*").unwrap();
assert!(vary.is_any());
}
#[test]
fn parse_fields() {
let vary = Vary::parse("Accept-Encoding, User-Agent").unwrap();
assert_eq!(
vary.fields(),
&["accept-encoding".to_string(), "user-agent".to_string()]
);
}
#[test]
fn parse_invalid() {
assert!(Vary::parse("bad value").is_err());
}
#[test]
fn parse_empty_elements() {
let vary = Vary::parse("").unwrap();
assert!(!vary.is_any());
assert!(vary.fields().is_empty());
let vary = Vary::parse(",").unwrap();
assert!(vary.fields().is_empty());
let vary = Vary::parse("Accept,,User-Agent").unwrap();
assert_eq!(vary.fields().len(), 2);
}
#[test]
fn parse_wildcard_in_list() {
let vary = Vary::parse("*, Accept").unwrap();
assert!(vary.is_any());
assert!(vary.fields().is_empty());
let vary = Vary::parse("Accept, *").unwrap();
assert!(vary.is_any());
assert!(vary.fields().is_empty());
}
}