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