kutil_http/headers/preferences/
preferences.rs

1use super::{preference::*, selector::*, weight::*};
2
3use {
4    kutil_std::collections::*,
5    std::{hash::*, iter::*, str::*},
6};
7
8//
9// Preferences
10//
11
12/// List of [Preference].
13#[derive(Clone, Debug)]
14pub struct Preferences<SelectionT>(pub Vec<Preference<SelectionT>>);
15
16impl<SelectionT> Preferences<SelectionT> {
17    /// Sort by weight (highest to lowest).
18    pub fn sorted(mut self) -> Self {
19        self.0.sort_by(|a, b| b.weight.cmp(&a.weight));
20        self
21    }
22
23    /// Parse, combine, and sort.
24    pub fn parse(representations: &Vec<&str>) -> Self
25    where
26        SelectionT: Clone + Eq + FromStr,
27    {
28        let preferences: Vec<_> = representations
29            .iter()
30            .flat_map(|representation| representation.split(","))
31            .filter_map(move |format| {
32                let mut split = format.splitn(2, ';');
33
34                let selector = (split.next().expect("next").trim()).parse().ok()?;
35
36                let weight = match split.next() {
37                    Some(weight) => Weight::parse(weight.trim())?,
38                    None => Weight::MAX,
39                };
40
41                Some(Preference::new(selector, weight))
42            })
43            .collect();
44
45        Self(preferences).sorted()
46    }
47
48    /// Select the most preferred allowance.
49    ///
50    /// If there is a tie, we will go by the order of allowances.
51    pub fn best<'own>(&'own self, allowances: &'own [SelectionT]) -> Option<&'own SelectionT>
52    where
53        SelectionT: Hash + Eq,
54    {
55        if self.0.is_empty() {
56            None
57        } else {
58            let mut candidates = FastHashSet::<&SelectionT>::with_capacity(allowances.len());
59
60            for (index, preference) in self.0.iter().enumerate() {
61                let selections = preference.selector.select(allowances);
62
63                if !selections.is_empty() {
64                    candidates.extend(selections);
65
66                    if preference.selector.is_specific() {
67                        // There might still be more candidates if there are other preferences
68                        // of equal weight (and they would be right after us)
69                        candidates.extend(self.select_tied(index, preference.weight, allowances));
70                    }
71
72                    break;
73                }
74            }
75
76            if candidates.len() == 1 {
77                // No contest
78                return Some(candidates.into_iter().next().expect("should be safe"));
79            } else if !candidates.is_empty() {
80                // Break the tie
81                for selection in allowances {
82                    if candidates.contains(&selection) {
83                        return Some(selection);
84                    }
85                }
86            }
87
88            None
89        }
90    }
91
92    /// Select the most preferred allowance.
93    ///
94    /// If there is a tie, we will go by the order of allowances.
95    ///
96    /// If no best preference can be found we'll return the first allowance. `allowances` must not
97    /// be empty!
98    pub fn best_or_first<'own>(&'own self, allowances: &'own [SelectionT]) -> &'own SelectionT
99    where
100        SelectionT: Hash + Eq,
101    {
102        assert!(allowances.len() > 0);
103        self.best(allowances).unwrap_or_else(|| &allowances[0])
104    }
105
106    // Selections with equal weight.
107    fn select_tied<'own>(
108        &'own self,
109        index: usize,
110        weight: Weight,
111        allowances: &'own [SelectionT],
112    ) -> FastHashSet<&'own SelectionT>
113    where
114        SelectionT: Hash + Eq,
115    {
116        let mut tied = FastHashSet::with_capacity(self.0.len() - index);
117
118        for preference in &self.0[index + 1..] {
119            if preference.weight != weight {
120                break;
121            }
122
123            tied.extend(preference.selector.select(allowances));
124        }
125
126        tied
127    }
128}