use std::fmt; use std::iter::FromIterator; use std::marker::PhantomData; use bytes::BytesMut; use util::TryFromValues; use HeaderValue; // A single `HeaderValue` that can flatten multiple values with commas. #[derive(Clone, PartialEq, Eq, Hash)] pub(crate) struct FlatCsv { pub(crate) value: HeaderValue, _marker: PhantomData, } pub(crate) trait Separator { const BYTE: u8; const CHAR: char; } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum Comma {} impl Separator for Comma { const BYTE: u8 = b','; const CHAR: char = ','; } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum SemiColon {} impl Separator for SemiColon { const BYTE: u8 = b';'; const CHAR: char = ';'; } impl FlatCsv { pub(crate) fn iter(&self) -> impl Iterator { self.value.to_str().ok().into_iter().flat_map(|value_str| { let mut in_quotes = false; value_str .split(move |c| { if in_quotes { if c == '"' { in_quotes = false; } false // dont split } else { if c == Sep::CHAR { true // split } else { if c == '"' { in_quotes = true; } false // dont split } } }) .map(|item| item.trim()) }) } } impl TryFromValues for FlatCsv { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { let flat = values.collect(); Ok(flat) } } impl From for FlatCsv { fn from(value: HeaderValue) -> Self { FlatCsv { value, _marker: PhantomData, } } } impl<'a, Sep> From<&'a FlatCsv> for HeaderValue { fn from(flat: &'a FlatCsv) -> HeaderValue { flat.value.clone() } } impl fmt::Debug for FlatCsv { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.value, f) } } impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv { fn from_iter(iter: I) -> Self where I: IntoIterator, { let mut values = iter.into_iter(); // Common case is there is only 1 value, optimize for that if let (1, Some(1)) = values.size_hint() { return values .next() .expect("size_hint claimed 1 item") .clone() .into(); } // Otherwise, there are multiple, so this should merge them into 1. let mut buf = values .next() .cloned() .map(|val| BytesMut::from(val.as_bytes())) .unwrap_or_else(|| BytesMut::new()); for val in values { buf.extend_from_slice(&[Sep::BYTE, b' ']); buf.extend_from_slice(val.as_bytes()); } let val = HeaderValue::from_maybe_shared(buf.freeze()).expect("comma separated HeaderValues are valid"); val.into() } } // TODO: would be great if there was a way to de-dupe these with above impl FromIterator for FlatCsv { fn from_iter(iter: I) -> Self where I: IntoIterator, { let mut values = iter.into_iter(); // Common case is there is only 1 value, optimize for that if let (1, Some(1)) = values.size_hint() { return values.next().expect("size_hint claimed 1 item").into(); } // Otherwise, there are multiple, so this should merge them into 1. let mut buf = values .next() .map(|val| BytesMut::from(val.as_bytes())) .unwrap_or_else(|| BytesMut::new()); for val in values { buf.extend_from_slice(&[Sep::BYTE, b' ']); buf.extend_from_slice(val.as_bytes()); } let val = HeaderValue::from_maybe_shared(buf.freeze()).expect("comma separated HeaderValues are valid"); val.into() } } #[cfg(test)] mod tests { use super::*; #[test] fn comma() { let val = HeaderValue::from_static("aaa, b; bb, ccc"); let csv = FlatCsv::::from(val); let mut values = csv.iter(); assert_eq!(values.next(), Some("aaa")); assert_eq!(values.next(), Some("b; bb")); assert_eq!(values.next(), Some("ccc")); assert_eq!(values.next(), None); } #[test] fn semicolon() { let val = HeaderValue::from_static("aaa; b, bb; ccc"); let csv = FlatCsv::::from(val); let mut values = csv.iter(); assert_eq!(values.next(), Some("aaa")); assert_eq!(values.next(), Some("b, bb")); assert_eq!(values.next(), Some("ccc")); assert_eq!(values.next(), None); } #[test] fn quoted_text() { let val = HeaderValue::from_static("foo=\"bar,baz\", sherlock=holmes"); let csv = FlatCsv::::from(val); let mut values = csv.iter(); assert_eq!(values.next(), Some("foo=\"bar,baz\"")); assert_eq!(values.next(), Some("sherlock=holmes")); assert_eq!(values.next(), None); } }