Skip to content

Commit 344c6ea

Browse files
committed
Add support for language tags
1 parent e0ae15c commit 344c6ea

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

src/content/language.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use crate::headers::HeaderValue;
2+
use std::fmt::{self, Display};
3+
4+
#[derive(Debug, Clone, PartialEq)]
5+
pub struct Language(Vec<String>);
6+
7+
impl Language {
8+
/// Parses a given string into its corresponding language.
9+
pub(crate) fn from_str(s: &str) -> Option<Language> {
10+
let s = s.trim();
11+
12+
// We're dealing with an empty string.
13+
if s.is_empty() {
14+
return None;
15+
}
16+
17+
Some(Self(s.split('-').map(|tag| tag.to_string()).collect()))
18+
}
19+
}
20+
21+
impl Display for Language {
22+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23+
let mut tags = self.0.iter();
24+
if let Some(tag) = tags.next() {
25+
write!(f, "{}", tag)?;
26+
27+
for tag in tags {
28+
write!(f, "-{}", tag)?;
29+
}
30+
}
31+
Ok(())
32+
}
33+
}
34+
35+
impl From<Language> for HeaderValue {
36+
fn from(language: Language) -> Self {
37+
let s = language.to_string();
38+
unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
39+
}
40+
}

src/content/language_proposal.rs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use crate::content::Language;
2+
use crate::ensure;
3+
use crate::headers::HeaderValue;
4+
use crate::utils::parse_weight;
5+
6+
use std::cmp::{Ordering, PartialEq};
7+
use std::ops::{Deref, DerefMut};
8+
9+
/// A proposed `Language` in `AcceptLanguage`.
10+
#[derive(Debug, Clone, Copy, PartialEq)]
11+
pub struct LanguageProposal {
12+
/// The proposed language.
13+
pub(crate) language: Language,
14+
15+
/// The weight of the proposal.
16+
///
17+
/// This is a number between 0.0 and 1.0, and is max 3 decimal points.
18+
weight: Option<f32>,
19+
}
20+
21+
impl LanguageProposal {
22+
/// Create a new instance of `LanguageProposal`.
23+
pub fn new(language: impl Into<Language>, weight: Option<f32>) -> crate::Result<Self> {
24+
if let Some(weight) = weight {
25+
ensure!(
26+
weight.is_sign_positive() && weight <= 1.0,
27+
"LanguageProposal should have a weight between 0.0 and 1.0"
28+
)
29+
}
30+
31+
Ok(Self {
32+
language: language.into(),
33+
weight,
34+
})
35+
}
36+
37+
/// Get the proposed language.
38+
pub fn language(&self) -> &Language {
39+
&self.language
40+
}
41+
42+
/// Get the weight of the proposal.
43+
pub fn weight(&self) -> Option<f32> {
44+
self.weight
45+
}
46+
47+
pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
48+
let mut parts = s.split(';');
49+
let language = match Language::from_str(parts.next().unwrap()) {
50+
Some(language) => language,
51+
None => return Ok(None),
52+
};
53+
let weight = parts.next().map(parse_weight).transpose()?;
54+
55+
Ok(Some(Self::new(language, weight)?))
56+
}
57+
}
58+
59+
impl From<Language> for LanguageProposal {
60+
fn from(language: Language) -> Self {
61+
Self {
62+
language,
63+
weight: None,
64+
}
65+
}
66+
}
67+
68+
impl PartialEq<Language> for LanguageProposal {
69+
fn eq(&self, other: &Language) -> bool {
70+
self.language == *other
71+
}
72+
}
73+
74+
impl PartialEq<Language> for &LanguageProposal {
75+
fn eq(&self, other: &Language) -> bool {
76+
self.language == *other
77+
}
78+
}
79+
80+
impl Deref for LanguageProposal {
81+
type Target = Language;
82+
fn deref(&self) -> &Self::Target {
83+
&self.language
84+
}
85+
}
86+
87+
impl DerefMut for LanguageProposal {
88+
fn deref_mut(&mut self) -> &mut Self::Target {
89+
&mut self.language
90+
}
91+
}
92+
93+
impl PartialOrd for LanguageProposal {
94+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
95+
match (self.weight, other.weight) {
96+
(Some(left), Some(right)) => left.partial_cmp(&right),
97+
(Some(_), None) => Some(Ordering::Greater),
98+
(None, Some(_)) => Some(Ordering::Less),
99+
(None, None) => None,
100+
}
101+
}
102+
}
103+
104+
impl From<LanguageProposal> for HeaderValue {
105+
fn from(entry: LanguageProposal) -> HeaderValue {
106+
let s = match entry.weight {
107+
Some(weight) => format!("{};q={:.3}", entry.language, weight),
108+
None => entry.language.to_string(),
109+
};
110+
unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
111+
}
112+
}

src/content/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ mod content_location;
4040
mod content_type;
4141
mod encoding;
4242
mod encoding_proposal;
43+
mod language;
44+
mod language_proposal;
4345
mod media_type_proposal;
4446

4547
#[doc(inline)]
@@ -53,4 +55,6 @@ pub use content_location::ContentLocation;
5355
pub use content_type::ContentType;
5456
pub use encoding::Encoding;
5557
pub use encoding_proposal::EncodingProposal;
58+
pub use language::Language;
59+
pub use language_proposal::LanguageProposal;
5660
pub use media_type_proposal::MediaTypeProposal;

0 commit comments

Comments
 (0)