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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*!
The symbols library parses the symbols data file and returns the collection of SymbolMetadata objects.
 */
pub mod extensions;

use std::path::PathBuf;

use serde::Deserialize;

#[allow(unused)]
#[derive(Debug, Default, Deserialize)]
pub struct SymbolMetadata {
    /// Exchange
    pub namespace: Option<String>,
    /// Symbol at the exchange
    pub symbol: String,
    /// The currency used to express the symbol's price.
    pub currency: Option<String>,
    /// The name of the price update provider.
    pub updater: Option<String>,
    /// The symbol, as used by the updater.
    pub updater_symbol: Option<String>,
    /// The symbol, as used in the Ledger journal.
    pub ledger_symbol: Option<String>,
    /// The symbol, as used at Interactive Brokers.
    pub ib_symbol: Option<String>,
    /// Remarks
    pub remarks: Option<String>
}

#[allow(unused)]
impl SymbolMetadata {
    pub fn new() -> Self {
        Self::default()
    }

    /// Returns ledger symbol, if exists, or the main symbol.
    pub fn get_symbol(&self) -> String {
        match &self.ledger_symbol {
            Some(ls) => ls.to_owned(),
            None => self.symbol.to_owned(),
        }
    }

    pub fn symbol_w_namespace(&self) -> String {
        match &self.namespace {
            Some(namespace) => format!("{}:{}", namespace, self.symbol),
            None => self.symbol.to_string(),
        }
    }
}

/// Read and parse the symbols collection.
pub fn read_symbols(path: &PathBuf) -> anyhow::Result<Vec<SymbolMetadata>> {
    //, options: Option<&ParseOptions>

    // read the file
    let mut rdr = csv::Reader::from_path(path)?;

    // parse
    // .records() = raw
    let collection: Vec<SymbolMetadata> = rdr.deserialize()
        .map(|result| match result {
            Ok(symbol) => symbol,
            Err(e) => panic!("Error deserializing: {e}"),
        })
        .collect();
    
    // Header row? Parsed by default.

    Ok(collection)
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use crate::read_symbols;

    #[test_log::test]
    fn read_test_file() {
        // println!("running in {:?}", std::env::current_dir());
        let path = PathBuf::from("tests/dummy.csv");
        println!("path: {:?}", path);

        let result = read_symbols(&path);
        assert!(result.is_ok());

        let actual = result.expect("parsed file");
        log::debug!("parsed: {:?}", actual);
        assert!(!actual.is_empty());
    }

    /// Confirm that the records get parsed into the struct
    #[test_log::test]
    fn test_parse() {
        let path = PathBuf::from("tests/dummy.csv");
        let list = read_symbols(&path).expect("parsed");

        assert_eq!(2, list.len());

        let actual = &list[0];

        assert_eq!("AUD", actual.symbol);
    }

    /// Panics if a row has less fields than the previous one.
    #[test_log::test]
    #[should_panic(expected="Error deserializing: CSV error: record 3 (line: 3, byte: 165): found record with 7 fields, but the previous record has 8 fields")]
    fn test_parsing_error() {
        let path = PathBuf::from("tests/dummy2.csv");

        let list = read_symbols(&path).expect("parsed");

        assert!(!list.is_empty());
    }

    #[test]
    fn test_get_symbol() {
        let path = PathBuf::from("tests/dummy.csv");
        let result = read_symbols(&path);
        let symbols = result.expect("parsed");

        let record = symbols.iter().find(|&symbol| symbol.symbol == "EL4X")
            .expect("found");

        let actual = record.get_symbol();

        assert_eq!("EL4X_DE", actual);
        assert_eq!("EL4X", record.symbol);
        assert_eq!(Some("EL4X_DE".to_owned()), record.ledger_symbol);
    }
}