spring_batch_rs/item/csv/
csv_reader.rs1use csv::{ReaderBuilder, StringRecordsIntoIter, Terminator, Trim};
2use serde::de::DeserializeOwned;
3use std::{cell::RefCell, fs::File, io::Read, path::Path};
4
5use crate::{
6 core::item::{ItemReader, ItemReaderResult},
7 error::BatchError,
8};
9
10pub struct CsvItemReader<R> {
12 records: RefCell<StringRecordsIntoIter<R>>,
13}
14
15impl<R: Read, T: DeserializeOwned> ItemReader<T> for CsvItemReader<R> {
16 fn read(&self) -> ItemReaderResult<T> {
22 if let Some(result) = self.records.borrow_mut().next() {
23 match result {
24 Ok(string_record) => {
25 let result: Result<T, _> = string_record.deserialize(None);
26
27 match result {
28 Ok(record) => Ok(Some(record)),
29 Err(error) => Err(BatchError::ItemReader(error.to_string())),
30 }
31 }
32 Err(error) => Err(BatchError::ItemReader(error.to_string())),
33 }
34 } else {
35 Ok(None)
36 }
37 }
38}
39
40#[derive(Default)]
42pub struct CsvItemReaderBuilder {
43 delimiter: u8,
44 terminator: Terminator,
45 has_headers: bool,
46}
47
48impl CsvItemReaderBuilder {
49 pub fn new() -> Self {
51 Self {
52 delimiter: b',',
53 terminator: Terminator::CRLF,
54 has_headers: false,
55 }
56 }
57
58 pub fn delimiter(mut self, delimiter: u8) -> Self {
60 self.delimiter = delimiter;
61 self
62 }
63
64 pub fn terminator(mut self, terminator: Terminator) -> Self {
66 self.terminator = terminator;
67 self
68 }
69
70 pub fn has_headers(mut self, yes: bool) -> Self {
72 self.has_headers = yes;
73 self
74 }
75
76 pub fn from_reader<R: Read>(self, rdr: R) -> CsvItemReader<R> {
78 let rdr = ReaderBuilder::new()
79 .trim(Trim::All)
80 .delimiter(self.delimiter)
81 .terminator(self.terminator)
82 .has_headers(self.has_headers)
83 .flexible(false)
84 .from_reader(rdr);
85
86 let records = rdr.into_records();
87
88 CsvItemReader {
89 records: RefCell::new(records),
90 }
91 }
92
93 pub fn from_path<R: AsRef<Path>>(self, path: R) -> CsvItemReader<File> {
95 let rdr = ReaderBuilder::new()
96 .trim(Trim::All)
97 .delimiter(self.delimiter)
98 .terminator(self.terminator)
99 .has_headers(self.has_headers)
100 .flexible(false)
101 .from_path(path);
102
103 let records = rdr.unwrap().into_records();
104
105 CsvItemReader {
106 records: RefCell::new(records),
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use std::error::Error;
114
115 use csv::StringRecord;
116
117 use crate::item::csv::csv_reader::CsvItemReaderBuilder;
118
119 #[test]
120 fn this_test_will_pass() -> Result<(), Box<dyn Error>> {
121 let data = "city,country,pop
122 Boston,United States,4628910
123 Concord,United States,42695";
124
125 let reader = CsvItemReaderBuilder::new()
126 .has_headers(true)
127 .delimiter(b',')
128 .from_reader(data.as_bytes());
129
130 let records = reader
131 .records
132 .into_inner()
133 .collect::<Result<Vec<StringRecord>, csv::Error>>()?;
134
135 assert_eq!(
136 records,
137 vec![
138 vec!["Boston", "United States", "4628910"],
139 vec!["Concord", "United States", "42695"],
140 ]
141 );
142
143 Ok(())
144 }
145}