headers/util/
flat_csv.rs

1use std::fmt;
2use std::iter::FromIterator;
3use std::marker::PhantomData;
4
5use bytes::BytesMut;
6use util::TryFromValues;
7use HeaderValue;
8
9// A single `HeaderValue` that can flatten multiple values with commas.
10#[derive(Clone, PartialEq, Eq, Hash)]
11pub(crate) struct FlatCsv<Sep = Comma> {
12    pub(crate) value: HeaderValue,
13    _marker: PhantomData<Sep>,
14}
15
16pub(crate) trait Separator {
17    const BYTE: u8;
18    const CHAR: char;
19}
20
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub(crate) enum Comma {}
23
24impl Separator for Comma {
25    const BYTE: u8 = b',';
26    const CHAR: char = ',';
27}
28
29#[derive(Clone, Debug, PartialEq, Eq, Hash)]
30pub(crate) enum SemiColon {}
31
32impl Separator for SemiColon {
33    const BYTE: u8 = b';';
34    const CHAR: char = ';';
35}
36
37impl<Sep: Separator> FlatCsv<Sep> {
38    pub(crate) fn iter(&self) -> impl Iterator<Item = &str> {
39        self.value.to_str().ok().into_iter().flat_map(|value_str| {
40            let mut in_quotes = false;
41            value_str
42                .split(move |c| {
43                    if in_quotes {
44                        if c == '"' {
45                            in_quotes = false;
46                        }
47                        false // dont split
48                    } else {
49                        if c == Sep::CHAR {
50                            true // split
51                        } else {
52                            if c == '"' {
53                                in_quotes = true;
54                            }
55                            false // dont split
56                        }
57                    }
58                })
59                .map(|item| item.trim())
60        })
61    }
62}
63
64impl<Sep: Separator> TryFromValues for FlatCsv<Sep> {
65    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
66    where
67        I: Iterator<Item = &'i HeaderValue>,
68    {
69        let flat = values.collect();
70        Ok(flat)
71    }
72}
73
74impl<Sep> From<HeaderValue> for FlatCsv<Sep> {
75    fn from(value: HeaderValue) -> Self {
76        FlatCsv {
77            value,
78            _marker: PhantomData,
79        }
80    }
81}
82
83impl<'a, Sep> From<&'a FlatCsv<Sep>> for HeaderValue {
84    fn from(flat: &'a FlatCsv<Sep>) -> HeaderValue {
85        flat.value.clone()
86    }
87}
88
89impl<Sep> fmt::Debug for FlatCsv<Sep> {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        fmt::Debug::fmt(&self.value, f)
92    }
93}
94
95impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv<Sep> {
96    fn from_iter<I>(iter: I) -> Self
97    where
98        I: IntoIterator<Item = &'a HeaderValue>,
99    {
100        let mut values = iter.into_iter();
101
102        // Common case is there is only 1 value, optimize for that
103        if let (1, Some(1)) = values.size_hint() {
104            return values
105                .next()
106                .expect("size_hint claimed 1 item")
107                .clone()
108                .into();
109        }
110
111        // Otherwise, there are multiple, so this should merge them into 1.
112        let mut buf = values
113            .next()
114            .cloned()
115            .map(|val| BytesMut::from(val.as_bytes()))
116            .unwrap_or_else(|| BytesMut::new());
117
118        for val in values {
119            buf.extend_from_slice(&[Sep::BYTE, b' ']);
120            buf.extend_from_slice(val.as_bytes());
121        }
122
123        let val = HeaderValue::from_maybe_shared(buf.freeze())
124            .expect("comma separated HeaderValues are valid");
125
126        val.into()
127    }
128}
129
130// TODO: would be great if there was a way to de-dupe these with above
131impl<Sep: Separator> FromIterator<HeaderValue> for FlatCsv<Sep> {
132    fn from_iter<I>(iter: I) -> Self
133    where
134        I: IntoIterator<Item = HeaderValue>,
135    {
136        let mut values = iter.into_iter();
137
138        // Common case is there is only 1 value, optimize for that
139        if let (1, Some(1)) = values.size_hint() {
140            return values.next().expect("size_hint claimed 1 item").into();
141        }
142
143        // Otherwise, there are multiple, so this should merge them into 1.
144        let mut buf = values
145            .next()
146            .map(|val| BytesMut::from(val.as_bytes()))
147            .unwrap_or_else(|| BytesMut::new());
148
149        for val in values {
150            buf.extend_from_slice(&[Sep::BYTE, b' ']);
151            buf.extend_from_slice(val.as_bytes());
152        }
153
154        let val = HeaderValue::from_maybe_shared(buf.freeze())
155            .expect("comma separated HeaderValues are valid");
156
157        val.into()
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn comma() {
167        let val = HeaderValue::from_static("aaa, b; bb, ccc");
168        let csv = FlatCsv::<Comma>::from(val);
169
170        let mut values = csv.iter();
171        assert_eq!(values.next(), Some("aaa"));
172        assert_eq!(values.next(), Some("b; bb"));
173        assert_eq!(values.next(), Some("ccc"));
174        assert_eq!(values.next(), None);
175    }
176
177    #[test]
178    fn semicolon() {
179        let val = HeaderValue::from_static("aaa; b, bb; ccc");
180        let csv = FlatCsv::<SemiColon>::from(val);
181
182        let mut values = csv.iter();
183        assert_eq!(values.next(), Some("aaa"));
184        assert_eq!(values.next(), Some("b, bb"));
185        assert_eq!(values.next(), Some("ccc"));
186        assert_eq!(values.next(), None);
187    }
188
189    #[test]
190    fn quoted_text() {
191        let val = HeaderValue::from_static("foo=\"bar,baz\", sherlock=holmes");
192        let csv = FlatCsv::<Comma>::from(val);
193
194        let mut values = csv.iter();
195        assert_eq!(values.next(), Some("foo=\"bar,baz\""));
196        assert_eq!(values.next(), Some("sherlock=holmes"));
197        assert_eq!(values.next(), None);
198    }
199}