Skip to content

Commit 9bfe795

Browse files
committed
WIP language ranges
1 parent e0ae15c commit 9bfe795

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

src/language/mod.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! RFC 4647 Language Ranges.
2+
//!
3+
//! [Read more](https://datatracker.ietf.org/doc/html/rfc4647)
4+
5+
mod parse;
6+
7+
use crate::headers::HeaderValue;
8+
use std::{fmt::{self, Display}, borrow::Cow, str::FromStr};
9+
10+
#[derive(Debug)]
11+
pub struct LanguageRange {
12+
pub(crate) tags: Vec<Cow<'static, str>>
13+
}
14+
15+
impl Display for LanguageRange {
16+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17+
let mut tags = self.tags.iter();
18+
if let Some(tag) = tags.next() {
19+
write!(f, "{}", tag)?;
20+
21+
for tag in tags {
22+
write!(f, "-{}", tag)?;
23+
}
24+
}
25+
Ok(())
26+
}
27+
}
28+
29+
impl From<LanguageRange> for HeaderValue {
30+
fn from(language: LanguageRange) -> Self {
31+
let s = language.to_string();
32+
unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
33+
}
34+
}
35+
36+
impl FromStr for LanguageRange {
37+
type Err = crate::Error;
38+
39+
fn from_str(s: &str) -> Result<Self, Self::Err> {
40+
parse::parse(s)
41+
}
42+
}

src/language/parse.rs

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::borrow::Cow;
2+
3+
use super::LanguageRange;
4+
5+
fn split_tag(input: &str) -> Option<(&str, &str)> {
6+
match input.find('-') {
7+
Some(pos) if pos <= 8 => {
8+
let (tag, rest) = input.split_at(pos);
9+
Some((tag, &rest[1..]))
10+
}
11+
Some(_) => None,
12+
None => (input.len() <= 8).then(|| (input, "")),
13+
}
14+
}
15+
16+
// language-range = (1*8ALPHA *("-" 1*8alphanum)) / "*"
17+
// alphanum = ALPHA / DIGIT
18+
pub(crate) fn parse(input: &str) -> crate::Result<LanguageRange> {
19+
let tags = if input == "*" {
20+
vec![Cow::from(input.to_string())]
21+
} else {
22+
let mut tags = Vec::new();
23+
24+
let (tag, mut input) = split_tag(input).ok_or_else(|| crate::format_err!("WIP error"))?;
25+
crate::ensure!(!tag.is_empty(), "Language tag should not be empty");
26+
crate::ensure!(
27+
tag.bytes()
28+
.all(|b| (b'a'..=b'z').contains(&b) || (b'A'..=b'Z').contains(&b)),
29+
"Language tag should be alpha"
30+
);
31+
tags.push(Cow::from(tag.to_string()));
32+
33+
while !input.is_empty() {
34+
let (tag, rest) = split_tag(input).ok_or_else(|| crate::format_err!("WIP error"))?;
35+
crate::ensure!(!tag.is_empty(), "Language tag should not be empty");
36+
crate::ensure!(
37+
tag.bytes().all(|b| (b'a'..=b'z').contains(&b)
38+
|| (b'A'..=b'Z').contains(&b)
39+
|| (b'0'..=b'9').contains(&b)),
40+
"Language tag should be alpha numeric"
41+
);
42+
tags.push(Cow::from(tag.to_string()));
43+
input = rest;
44+
}
45+
46+
tags
47+
};
48+
49+
Ok(LanguageRange { tags })
50+
}
51+
52+
#[test]
53+
fn test() {
54+
let range = parse("*").unwrap();
55+
assert_eq!(&range.tags, &["*"]);
56+
57+
let range = parse("en").unwrap();
58+
assert_eq!(&range.tags, &["en"]);
59+
60+
let range = parse("en-CA").unwrap();
61+
assert_eq!(&range.tags, &["en", "CA"]);
62+
63+
let range = parse("zh-Hant-CN-x-private1-private2").unwrap();
64+
assert_eq!(
65+
&range.tags,
66+
&["zh", "Hant", "CN", "x", "private1", "private2"]
67+
);
68+
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ pub mod cache;
123123
pub mod conditional;
124124
pub mod content;
125125
pub mod headers;
126+
pub mod language;
126127
pub mod mime;
127128
pub mod other;
128129
pub mod proxies;

0 commit comments

Comments
 (0)