ferron_common/util/
parse_q_value_header.rs

1use std::{cmp::Ordering, str::FromStr};
2
3#[derive(Debug, PartialEq)]
4struct HeaderValue {
5  value: String,
6  q_value: Option<f32>,
7}
8
9impl FromStr for HeaderValue {
10  type Err = &'static str;
11
12  fn from_str(s: &str) -> Result<Self, Self::Err> {
13    let parts: Vec<&str> = s.split(';').collect();
14    let value = parts[0].trim().to_string();
15
16    let q_value = parts.get(1).map(|part| {
17      part
18        .trim()
19        .strip_prefix("q=")
20        .unwrap_or("0")
21        .parse::<f32>()
22        .unwrap_or(0.0)
23    });
24
25    Ok(HeaderValue { value, q_value })
26  }
27}
28
29pub fn parse_q_value_header(header: &str) -> Vec<String> {
30  let mut values: Vec<HeaderValue> = header
31    .split(',')
32    .filter_map(|s| HeaderValue::from_str(s.trim()).ok())
33    .collect();
34
35  let mut last_some_q_value = None;
36  for value in values.iter_mut().rev() {
37    if value.q_value.is_none() {
38      value.q_value = Some(last_some_q_value.unwrap_or(1.0));
39    } else {
40      last_some_q_value = value.q_value;
41    }
42  }
43
44  values.sort_by(|a, b| b.q_value.partial_cmp(&a.q_value).unwrap_or(Ordering::Equal));
45
46  values.into_iter().map(|v| v.value).collect::<Vec<String>>()
47}
48
49#[cfg(test)]
50mod tests {
51  use super::*;
52
53  #[test]
54  fn test_parse_q_value_header() {
55    let header = "text/html; q=0.8, text/plain; q=0.5, text/xml; q=0.3";
56    let expected = vec!["text/html", "text/plain", "text/xml"];
57    assert_eq!(parse_q_value_header(header), expected);
58  }
59
60  #[test]
61  fn test_parse_q_value_header_with_out_of_order_and_sparse_q_values() {
62    let header = "text/html; q=0.8, application/javascript, text/javascript; q=0.4, text/plain; q=0.5, text/xml; q=0.3";
63    let expected = vec![
64      "text/html",
65      "text/plain",
66      "application/javascript",
67      "text/javascript",
68      "text/xml",
69    ];
70    assert_eq!(parse_q_value_header(header), expected);
71  }
72}