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#[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 } else {
49 if c == Sep::CHAR {
50 true } else {
52 if c == '"' {
53 in_quotes = true;
54 }
55 false }
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 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 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
130impl<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 if let (1, Some(1)) = values.size_hint() {
140 return values.next().expect("size_hint claimed 1 item").into();
141 }
142
143 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}