use crate::content_type::ContentType;
use crate::csv_record_codec::{CsvFrameConfig, CsvRecordCodec};
use crate::error::{StreamError, StreamErrorKind};
use crate::format::{
DecodeOptions, DefaultFormat, FrameParser, ItemEncoder, StreamFormat, StreamFormatDecode,
StreamFormatEncode,
};
use bytes::BytesMut;
use serde::{Deserialize, Serialize};
const CSV_CONTENT_TYPE: &str = "text/csv";
const CSV_ALIASES: &[&str] = &["text/csv", "application/csv"];
#[derive(Debug, Clone)]
pub struct CsvStreamFormat {
has_headers: bool,
delimiter: u8,
flexible: bool,
quote_style: csv::QuoteStyle,
quote: u8,
double_quote: bool,
escape: u8,
terminator: csv::Terminator,
}
impl Default for CsvStreamFormat {
fn default() -> Self {
Self {
has_headers: true,
delimiter: b',',
flexible: false,
quote_style: csv::QuoteStyle::Necessary,
quote: b'"',
double_quote: true,
escape: b'\\',
terminator: csv::Terminator::Any(b'\n'),
}
}
}
impl DefaultFormat for CsvStreamFormat {
fn default_format() -> Self {
Self::default()
}
}
impl CsvStreamFormat {
pub fn new(has_headers: bool, delimiter: u8) -> Self {
Self {
has_headers,
delimiter,
..Default::default()
}
}
pub fn with_flexible(mut self, flexible: bool) -> Self {
self.flexible = flexible;
self
}
pub fn with_quote_style(mut self, quote_style: csv::QuoteStyle) -> Self {
self.quote_style = quote_style;
self
}
pub fn with_quote(mut self, quote: u8) -> Self {
self.quote = quote;
self
}
pub fn with_double_quote(mut self, double_quote: bool) -> Self {
self.double_quote = double_quote;
self
}
pub fn with_escape(mut self, escape: u8) -> Self {
self.escape = escape;
self
}
pub fn with_terminator(mut self, terminator: csv::Terminator) -> Self {
self.terminator = terminator;
self
}
pub fn with_delimiter(mut self, delimiter: u8) -> Self {
self.delimiter = delimiter;
self
}
pub fn with_has_headers(mut self, has_headers: bool) -> Self {
self.has_headers = has_headers;
self
}
fn writer_builder(&self, write_headers: bool) -> csv::WriterBuilder {
let mut builder = csv::WriterBuilder::new();
builder
.has_headers(write_headers)
.delimiter(self.delimiter)
.flexible(self.flexible)
.quote_style(self.quote_style)
.quote(self.quote)
.double_quote(self.double_quote)
.escape(self.escape)
.terminator(self.terminator);
builder
}
fn frame_config(&self) -> CsvFrameConfig {
CsvFrameConfig {
delimiter: self.delimiter,
quote: self.quote,
double_quote: self.double_quote,
escape: if self.double_quote {
None
} else {
Some(self.escape)
},
terminator: match self.terminator {
csv::Terminator::CRLF => csv_core::Terminator::CRLF,
csv::Terminator::Any(b) => csv_core::Terminator::Any(b),
_ => csv_core::Terminator::Any(b'\n'),
},
}
}
}
impl StreamFormat for CsvStreamFormat {
fn format_name(&self) -> &'static str {
"csv"
}
fn default_content_type(&self) -> &'static str {
CSV_CONTENT_TYPE
}
fn accepts_content_type(&self, ct: &ContentType<'_>) -> bool {
ct.matches_any(CSV_ALIASES)
}
}
pub struct CsvEncoder {
format: CsvStreamFormat,
}
impl<T> ItemEncoder<T> for CsvEncoder
where
T: Serialize,
{
fn encode(&mut self, item: &T, index: u64, buf: &mut BytesMut) -> Result<(), StreamError> {
let write_headers = index == 0 && self.format.has_headers;
let mut writer = self
.format
.writer_builder(write_headers)
.from_writer(Vec::new());
writer
.serialize(item)
.map_err(|err| StreamError::new(StreamErrorKind::CodecError, Some(Box::new(err)), None))?;
writer
.flush()
.map_err(|err| StreamError::new(StreamErrorKind::CodecError, Some(Box::new(err)), None))?;
let bytes = writer
.into_inner()
.map_err(|err| StreamError::new(StreamErrorKind::CodecError, Some(Box::new(err)), None))?;
buf.extend_from_slice(&bytes);
Ok(())
}
}
impl<T> StreamFormatEncode<T> for CsvStreamFormat
where
T: Serialize,
{
type Encoder = CsvEncoder;
fn encoder(&self) -> Self::Encoder {
CsvEncoder {
format: self.clone(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CsvParser;
impl<T> FrameParser<csv::ByteRecord, T> for CsvParser
where
T: for<'de> Deserialize<'de>,
{
fn parse(&self, frame: csv::ByteRecord) -> Result<T, StreamError> {
frame.deserialize(None).map_err(|err| {
StreamError::new(StreamErrorKind::CodecError, Some(Box::new(err)), None)
})
}
}
impl<T> StreamFormatDecode<T> for CsvStreamFormat
where
T: for<'de> Deserialize<'de>,
{
type Frame = csv::ByteRecord;
type Framer = CsvRecordCodec;
type Parser = CsvParser;
fn framer(&self, options: &DecodeOptions) -> Self::Framer {
CsvRecordCodec::new(self.frame_config(), self.has_headers, options.max_obj_len)
}
fn parser(&self) -> Self::Parser {
CsvParser
}
}