rusty_s3/
map.rs

1use std::borrow::Cow;
2use std::fmt::{self, Debug};
3
4/// A map used for holding query string paramenters or headers
5#[derive(Clone)]
6pub struct Map<'a> {
7    inner: Vec<(Cow<'a, str>, Cow<'a, str>)>,
8}
9
10impl<'a> Map<'a> {
11    /// Construct a new empty `Map`
12    #[inline]
13    #[must_use]
14    pub const fn new() -> Self {
15        Self { inner: Vec::new() }
16    }
17
18    /// Get the number of elements in this `Map`
19    #[inline]
20    #[must_use]
21    pub fn len(&self) -> usize {
22        self.inner.len()
23    }
24
25    /// Return `true` if this `Map` is empty
26    #[inline]
27    #[must_use]
28    pub fn is_empty(&self) -> bool {
29        self.inner.is_empty()
30    }
31
32    /// Get the value of an element of this `Map`, or `None` if it doesn't contain `key`
33    #[must_use]
34    pub fn get(&self, key: &str) -> Option<&str> {
35        self.inner
36            .binary_search_by(|a| a.0.as_ref().cmp(key))
37            .map_or(None, |i| self.inner.get(i).map(|kv| kv.1.as_ref()))
38    }
39
40    /// Insert a new element in this `Map`
41    ///
42    /// If the `key` is already present, the `value` overwrites the existing value:
43    ///
44    /// ```
45    /// let mut map = rusty_s3::Map::new();
46    /// map.insert("k", "a");
47    /// assert_eq!(map.get("k"), Some("a"));
48    /// map.insert("k", "b");
49    /// assert_eq!(map.get("k"), Some("b"));
50    /// ```
51    ///
52    /// # Panics
53    ///
54    /// In case of out of bound inner index access
55    pub fn insert<K, V>(&mut self, key: K, value: V)
56    where
57        K: Into<Cow<'a, str>>,
58        V: Into<Cow<'a, str>>,
59    {
60        let key = key.into();
61        let value = value.into();
62
63        let i = self.inner.binary_search_by(|a| a.0.cmp(&key));
64        match i {
65            Ok(i) => {
66                let old_value = self.inner.get_mut(i).expect("i can't be out of bounds");
67                *old_value = (key, value);
68            }
69            Err(i) => self.inner.insert(i, (key, value)),
70        }
71    }
72
73    /// Insert a new element in this `Map`
74    ///
75    /// If the `key` is already present, the `value` is appended to the existing value:
76    ///
77    /// ```
78    /// let mut map = rusty_s3::Map::new();
79    /// map.append("k", "a");
80    /// assert_eq!(map.get("k"), Some("a"));
81    /// map.append("k", "b");
82    /// assert_eq!(map.get("k"), Some("a, b"));
83    /// ```
84    ///
85    /// # Panics
86    ///
87    /// In case of out of bound inner index access
88    pub fn append<K, V>(&mut self, key: K, value: V)
89    where
90        K: Into<Cow<'a, str>>,
91        V: Into<Cow<'a, str>>,
92    {
93        let key = key.into();
94        let value = value.into();
95
96        let i = self.inner.binary_search_by(|a| a.0.cmp(&key));
97        match i {
98            Ok(i) => {
99                let old_value = self.inner.get_mut(i).expect("i can't be out of bounds");
100                let new_value = Cow::Owned(format!("{}, {}", old_value.1, value));
101                *old_value = (key, new_value);
102            }
103            Err(i) => self.inner.insert(i, (key, value)),
104        }
105    }
106
107    /// Remove an element from this `Map` and return it
108    pub fn remove(&mut self, key: &str) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
109        match self.inner.binary_search_by(|a| a.0.as_ref().cmp(key)) {
110            Ok(i) => Some(self.inner.remove(i)),
111            Err(_) => None,
112        }
113    }
114
115    /// Return an `Iterator` over this map
116    ///
117    /// The elements are always sorted in alphabetical order based on the key.
118    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + Clone {
119        self.inner.iter().map(|t| (t.0.as_ref(), t.1.as_ref()))
120    }
121}
122
123impl Debug for Map<'_> {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        f.debug_map().entries(self.iter()).finish()
126    }
127}
128
129impl Default for Map<'_> {
130    #[inline]
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use pretty_assertions::assert_eq;
139
140    use super::*;
141
142    #[test]
143    fn map() {
144        let mut map = Map::new();
145        {
146            assert_eq!(map.len(), 0);
147            assert!(map.is_empty());
148            assert!(map.get("nothing").is_none());
149
150            let mut iter = map.iter();
151            assert!(iter.next().is_none());
152        }
153
154        {
155            map.insert("content-type", "text/plain");
156            assert_eq!(map.len(), 1);
157            assert!(!map.is_empty());
158            assert!(map.get("nothing").is_none());
159            assert_eq!(map.get("content-type"), Some("text/plain"));
160
161            let iter = map.iter();
162            iter.eq(vec![("content-type", "text/plain")]);
163        }
164
165        {
166            map.insert("cache-control", "public, max-age=86400");
167            assert_eq!(map.len(), 2);
168            assert!(!map.is_empty());
169            assert!(map.get("nothing").is_none());
170            assert_eq!(map.get("content-type"), Some("text/plain"));
171            assert_eq!(map.get("cache-control"), Some("public, max-age=86400"));
172
173            let iter = map.iter();
174            iter.eq(vec![
175                ("cache-control", "public, max-age=86400"),
176                ("content-type", "text/plain"),
177            ]);
178        }
179
180        {
181            map.insert("x-amz-storage-class", "standard");
182            assert_eq!(map.len(), 3);
183            assert!(!map.is_empty());
184            assert!(map.get("nothing").is_none());
185            assert_eq!(map.get("content-type"), Some("text/plain"));
186            assert_eq!(map.get("cache-control"), Some("public, max-age=86400"));
187            assert_eq!(map.get("x-amz-storage-class"), Some("standard"));
188
189            let iter = map.iter();
190            iter.eq(vec![
191                ("cache-control", "public, max-age=86400"),
192                ("content-type", "text/plain"),
193                ("x-amz-storage-class", "standard"),
194            ]);
195        }
196
197        {
198            map.remove("content-type");
199            assert_eq!(map.len(), 2);
200            assert!(!map.is_empty());
201            assert!(map.get("nothing").is_none());
202            assert_eq!(map.get("cache-control"), Some("public, max-age=86400"));
203            assert_eq!(map.get("x-amz-storage-class"), Some("standard"));
204
205            let iter = map.iter();
206            iter.eq(vec![
207                ("cache-control", "public, max-age=86400"),
208                ("x-amz-storage-class", "standard"),
209            ]);
210        }
211
212        {
213            map.remove("x-amz-look-at-how-many-headers-you-have");
214            assert_eq!(map.len(), 2);
215            assert!(!map.is_empty());
216            assert!(map.get("nothing").is_none());
217            assert_eq!(map.get("cache-control"), Some("public, max-age=86400"));
218            assert_eq!(map.get("x-amz-storage-class"), Some("standard"));
219
220            let iter = map.iter();
221            iter.eq(vec![
222                ("cache-control", "public, max-age=86400"),
223                ("x-amz-storage-class", "standard"),
224            ]);
225        }
226
227        {
228            map.append("cache-control", "immutable");
229            assert_eq!(map.len(), 2);
230            assert!(!map.is_empty());
231            assert!(map.get("nothing").is_none());
232            assert_eq!(
233                map.get("cache-control"),
234                Some("public, max-age=86400, immutable")
235            );
236            assert_eq!(map.get("x-amz-storage-class"), Some("standard"));
237
238            let iter = map.iter();
239            iter.eq(vec![
240                ("cache-control", "public, max-age=86400, immutable"),
241                ("x-amz-storage-class", "standard"),
242            ]);
243        }
244    }
245}