sip_header/
accept_language.rs1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub struct SipAcceptLanguageEntry {
9 language: String,
10 params: Vec<(String, String)>,
11}
12
13impl SipAcceptLanguageEntry {
14 pub fn language(&self) -> &str {
16 &self.language
17 }
18
19 pub fn params(&self) -> &[(String, String)] {
21 &self.params
22 }
23
24 pub fn param(&self, key: &str) -> Option<&str> {
26 self.params
27 .iter()
28 .find(|(k, _)| k.eq_ignore_ascii_case(key))
29 .map(|(_, v)| v.as_str())
30 }
31
32 pub fn q(&self) -> Option<&str> {
34 self.param("q")
35 }
36}
37
38impl fmt::Display for SipAcceptLanguageEntry {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "{}", self.language)?;
41 for (key, value) in &self.params {
42 write!(f, ";{key}={value}")?;
43 }
44 Ok(())
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum SipAcceptLanguageError {
52 Empty,
54 InvalidFormat(String),
56}
57
58impl fmt::Display for SipAcceptLanguageError {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Self::Empty => write!(f, "empty Accept-Language header value"),
62 Self::InvalidFormat(raw) => write!(f, "invalid Accept-Language entry: {raw}"),
63 }
64 }
65}
66
67impl std::error::Error for SipAcceptLanguageError {}
68
69fn parse_entry(raw: &str) -> Result<SipAcceptLanguageEntry, SipAcceptLanguageError> {
70 let raw = raw.trim();
71 if raw.is_empty() {
72 return Err(SipAcceptLanguageError::InvalidFormat(raw.to_string()));
73 }
74
75 let (lang_part, params_part) = match raw.split_once(';') {
76 Some((l, p)) => (l.trim(), Some(p)),
77 None => (raw, None),
78 };
79
80 if lang_part.is_empty() {
81 return Err(SipAcceptLanguageError::InvalidFormat(raw.to_string()));
82 }
83
84 let language = lang_part.to_ascii_lowercase();
85 let mut params = Vec::new();
86
87 if let Some(params_str) = params_part {
88 for segment in params_str.split(';') {
89 let segment = segment.trim();
90 if segment.is_empty() {
91 continue;
92 }
93 if let Some((key, value)) = segment.split_once('=') {
94 params.push((
95 key.trim()
96 .to_ascii_lowercase(),
97 value
98 .trim()
99 .to_string(),
100 ));
101 } else {
102 params.push((segment.to_ascii_lowercase(), String::new()));
103 }
104 }
105 }
106
107 Ok(SipAcceptLanguageEntry { language, params })
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112#[non_exhaustive]
113pub struct SipAcceptLanguage(Vec<SipAcceptLanguageEntry>);
114
115impl SipAcceptLanguage {
116 pub fn parse(raw: &str) -> Result<Self, SipAcceptLanguageError> {
118 let raw = raw.trim();
119 if raw.is_empty() {
120 return Err(SipAcceptLanguageError::Empty);
121 }
122 let entries: Vec<_> = crate::split_comma_entries(raw)
123 .into_iter()
124 .map(parse_entry)
125 .collect::<Result<_, _>>()?;
126 if entries.is_empty() {
127 return Err(SipAcceptLanguageError::Empty);
128 }
129 Ok(Self(entries))
130 }
131
132 pub fn entries(&self) -> &[SipAcceptLanguageEntry] {
134 &self.0
135 }
136
137 pub fn into_entries(self) -> Vec<SipAcceptLanguageEntry> {
139 self.0
140 }
141
142 pub fn len(&self) -> usize {
144 self.0
145 .len()
146 }
147
148 pub fn is_empty(&self) -> bool {
150 self.0
151 .is_empty()
152 }
153}
154
155impl fmt::Display for SipAcceptLanguage {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 crate::fmt_joined(f, &self.0, ", ")
158 }
159}
160
161impl_from_str_via_parse!(SipAcceptLanguage, SipAcceptLanguageError);
162
163impl<'a> IntoIterator for &'a SipAcceptLanguage {
164 type Item = &'a SipAcceptLanguageEntry;
165 type IntoIter = std::slice::Iter<'a, SipAcceptLanguageEntry>;
166
167 fn into_iter(self) -> Self::IntoIter {
168 self.0
169 .iter()
170 }
171}
172
173impl IntoIterator for SipAcceptLanguage {
174 type Item = SipAcceptLanguageEntry;
175 type IntoIter = std::vec::IntoIter<SipAcceptLanguageEntry>;
176
177 fn into_iter(self) -> Self::IntoIter {
178 self.0
179 .into_iter()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn single_language() {
189 let al = SipAcceptLanguage::parse("en").unwrap();
190 assert_eq!(al.len(), 1);
191 assert_eq!(al.entries()[0].language(), "en");
192 }
193
194 #[test]
195 fn multiple_languages_with_q() {
196 let al = SipAcceptLanguage::parse("en;q=0.9, fr;q=0.8, *;q=0.1").unwrap();
197 assert_eq!(al.len(), 3);
198 assert_eq!(al.entries()[0].language(), "en");
199 assert_eq!(al.entries()[1].q(), Some("0.8"));
200 assert_eq!(al.entries()[2].language(), "*");
201 }
202
203 #[test]
204 fn language_subtag() {
205 let al = SipAcceptLanguage::parse("en-US").unwrap();
206 assert_eq!(al.entries()[0].language(), "en-us");
207 }
208
209 #[test]
210 fn empty_input() {
211 assert!(matches!(
212 SipAcceptLanguage::parse(""),
213 Err(SipAcceptLanguageError::Empty)
214 ));
215 }
216
217 #[test]
218 fn from_str() {
219 let al: SipAcceptLanguage = "en"
220 .parse()
221 .unwrap();
222 assert_eq!(al.len(), 1);
223 }
224
225 #[test]
226 fn display_roundtrip() {
227 let raw = "en;q=0.9";
228 let al = SipAcceptLanguage::parse(raw).unwrap();
229 assert_eq!(al.to_string(), raw);
230 }
231}