use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContentLanguageError {
Empty,
InvalidFormat,
InvalidLanguageTag,
}
impl fmt::Display for ContentLanguageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContentLanguageError::Empty => write!(f, "empty Content-Language"),
ContentLanguageError::InvalidFormat => {
write!(f, "invalid Content-Language format")
}
ContentLanguageError::InvalidLanguageTag => {
write!(f, "invalid language tag")
}
}
}
}
impl std::error::Error for ContentLanguageError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentLanguage {
tags: Vec<String>,
}
impl ContentLanguage {
pub fn parse(input: &str) -> Result<Self, ContentLanguageError> {
let input = input.trim();
let mut tags = Vec::new();
for part in input.split(',') {
let tag = part.trim();
if tag.is_empty() {
continue;
}
if !is_valid_language_tag(tag) {
return Err(ContentLanguageError::InvalidLanguageTag);
}
tags.push(tag.to_string());
}
Ok(ContentLanguage { tags })
}
pub fn tags(&self) -> &[String] {
&self.tags
}
}
impl fmt::Display for ContentLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.tags.join(", "))
}
}
fn is_valid_language_tag(tag: &str) -> bool {
if tag.is_empty() {
return false;
}
let mut parts = tag.split('-');
if let Some(primary) = parts.next() {
if primary.is_empty()
|| primary.len() > 8
|| !primary.chars().all(|c| c.is_ascii_alphabetic())
{
return false;
}
} else {
return false;
}
for part in parts {
if part.is_empty() || part.len() > 8 || !part.chars().all(|c| c.is_ascii_alphanumeric()) {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_simple() {
let cl = ContentLanguage::parse("en").unwrap();
assert_eq!(cl.tags(), &["en".to_string()]);
}
#[test]
fn parse_multiple() {
let cl = ContentLanguage::parse("en-US, ja").unwrap();
assert_eq!(cl.tags().len(), 2);
assert_eq!(cl.tags()[1], "ja");
}
#[test]
fn parse_invalid() {
assert!(ContentLanguage::parse("en-").is_err());
assert!(ContentLanguage::parse("en--us").is_err());
}
#[test]
fn parse_empty_elements() {
let cl = ContentLanguage::parse("").unwrap();
assert!(cl.tags().is_empty());
let cl = ContentLanguage::parse(",").unwrap();
assert!(cl.tags().is_empty());
let cl = ContentLanguage::parse("en,,ja").unwrap();
assert_eq!(cl.tags().len(), 2);
}
#[test]
fn display() {
let cl = ContentLanguage::parse("en-US, ja").unwrap();
assert_eq!(cl.to_string(), "en-US, ja");
}
#[test]
fn parse_primary_subtag_alpha_only() {
assert!(ContentLanguage::parse("123").is_err());
assert!(ContentLanguage::parse("1ab").is_err());
let cl = ContentLanguage::parse("en-123").unwrap();
assert_eq!(cl.tags()[0], "en-123");
}
}