1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use std::iter::Iterator;
use std::str::FromStr;

use csv::StringRecord;

use crate::broker_statement::ib::StatementParser;
use crate::core::{EmptyResult, GenericResult};
use crate::currency::Cash;
use crate::types::{Date, DateTime, Decimal};
use crate::util::{self, DecimalRestrictions};

pub struct RecordSpec<'a> {
    pub name: &'a str,
    fields: Vec<&'a str>,
    offset: usize,
}

impl<'a> RecordSpec<'a> {
    pub fn new(name: &'a str, fields: Vec<&'a str>, offset: usize) -> RecordSpec<'a> {
        RecordSpec {name, fields, offset}
    }
}

pub struct Record<'a> {
    pub spec: &'a RecordSpec<'a>,
    pub values: &'a StringRecord,
}

impl<'a> Record<'a> {
    pub fn new(spec: &'a RecordSpec<'a>, values: &'a StringRecord) -> Record<'a> {
        Record {spec, values}
    }

    pub fn get_value(&self, field: &str) -> GenericResult<&str> {
        if let Some(index) = self.spec.fields.iter().position(|other: &&str| *other == field) {
            if let Some(value) = self.values.get(self.spec.offset + index) {
                return Ok(value);
            }
        }

        Err!("{:?} record doesn't have {:?} field", self.spec.name, field)
    }

    pub fn check_value(&self, field: &str, value: &str) -> EmptyResult {
        self.check_values(&[(field, value)])
    }

    pub fn check_values(&self, values: &[(&str, &str)]) -> EmptyResult {
        for (field, value) in values.iter() {
            if self.get_value(*field)? != *value {
                return Err!("Got an unexpected {:?} field value: {:?}", *field, *value);
            }
        }

        Ok(())
    }

    pub fn parse_value<T: FromStr>(&self, field: &str) -> GenericResult<T> {
        let value = self.get_value(field)?;
        Ok(value.parse().map_err(|_| format!(
            "{:?} field has an invalid value: {:?}", field, value))?)
    }

    pub fn parse_date(&self, field: &str) -> GenericResult<Date> {
        parse_date(self.get_value(field)?)
    }

    pub fn parse_amount(&self, field: &str, restrictions: DecimalRestrictions) -> GenericResult<Decimal> {
        let value = self.get_value(field)?;
        Ok(util::parse_decimal(&value.replace(',', ""), restrictions).map_err(|_| format!(
            "Invalid amount: {:?}", value))?)
    }

    pub fn parse_cash(&self, field: &str, currency: &str, restrictions: DecimalRestrictions) -> GenericResult<Cash> {
        Ok(Cash::new(currency, self.parse_amount(field, restrictions)?))
    }
}

pub trait RecordParser {
    fn data_types(&self) -> Option<&'static [&'static str]> { Some(&["Data"]) }
    fn skip_data_types(&self) -> Option<&'static [&'static str]> { None }
    fn skip_totals(&self) -> bool { false }
    fn parse(&self, parser: &mut StatementParser, record: &Record) -> EmptyResult;
}

pub fn format_record<'a, I>(iter: I) -> String
    where I: IntoIterator<Item = &'a str> {

    iter.into_iter()
        .map(|value| format!("{:?}", value))
        .collect::<Vec<_>>()
        .join(", ")
}

fn parse_date(date: &str) -> GenericResult<Date> {
    util::parse_date(date, "%Y-%m-%d")
}

pub fn parse_date_time(date_time: &str) -> GenericResult<DateTime> {
    util::parse_date_time(date_time, "%Y-%m-%d, %H:%M:%S")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn date_parsing() {
        assert_eq!(parse_date("2018-06-22").unwrap(), date!(22, 6, 2018));
    }

    #[test]
    fn time_parsing() {
        assert_eq!(parse_date_time("2018-07-31, 13:09:47").unwrap(), date!(31, 7, 2018).and_hms(13, 9, 47));
    }
}