1use 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 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 fn sanitize_csv(reader: impl Read) -> csv::Result<NamedTempFile> {
36 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 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}