bitpanda_csv/
parser.rs

1//! # Parser
2//!
3//! Bitpanda CSV parser
4
5use crate::Trade;
6
7use std::fs::File;
8use std::io::{BufRead, BufReader, Read, Write};
9use tempfile::NamedTempFile;
10
11const BITPANDA_CSV_COL_HEADER: &str = r#""Transaction ID",Timestamp,"Transaction Type",In/Out,"Amount Fiat",Fiat,"Amount Asset",Asset,"Asset market price","Asset market price currency","Asset class","Product ID",Fee,"Fee asset",Spread,"Spread Currency""#;
12
13pub struct BitpandaTradeParser;
14
15impl BitpandaTradeParser {
16    /// Parse CSV from file at path after sanitizing it
17    pub fn parse(reader: impl Read) -> csv::Result<Vec<Trade>> {
18        debug!("parsing CSV...");
19        let sanitized_csv = Self::sanitize_csv(reader)?;
20        debug!("parsing CSV data from {}", sanitized_csv.path().display());
21        let file = File::open(sanitized_csv.path())?;
22        debug!("tempfile opened");
23        let mut reader = csv::Reader::from_reader(file);
24        let mut trades: Vec<Trade> = Vec::new();
25        for trade in reader.deserialize::<Trade>() {
26            let trade = trade?;
27            debug!("found trade {:?}", trade);
28            trades.push(trade);
29        }
30        info!("found {} trades in CSV file", trades.len());
31        Ok(trades)
32    }
33
34    /// Sanitize the trades csv keeping only the lines after the columns headers
35    fn sanitize_csv(reader: impl Read) -> csv::Result<NamedTempFile> {
36        // open tempfile
37        debug!("opening tempfile");
38        let work_file = NamedTempFile::new()?;
39        let mut writer = File::create(work_file.path())?;
40        debug!("tempfile opened at {}", work_file.path().display());
41        // iter reader lines
42        let mut keep = false;
43        for line in BufReader::new(reader).lines() {
44            let line = line?;
45            if line == BITPANDA_CSV_COL_HEADER {
46                debug!("found column header");
47                keep = true;
48            }
49            if keep {
50                writeln!(writer, "{}", line)?;
51            }
52        }
53        debug!("csv file sanitized");
54        Ok(work_file)
55    }
56}
57
58#[cfg(test)]
59mod test {
60
61    use super::*;
62
63    use pretty_assertions::assert_eq;
64    use std::path::Path;
65
66    #[test]
67    fn should_parse_csv_data() {
68        let reader = File::open(Path::new("./test/bitpanda.csv")).unwrap();
69        let trades = BitpandaTradeParser::parse(reader).unwrap();
70        assert_eq!(trades.len(), 12);
71    }
72}