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
use csv;
use jormungandr_lib::interfaces::BlockDate;
use serde::Deserialize;
use std::{ffi::OsStr, fmt, path::Path};
use thiserror::Error;

#[derive(Debug, Deserialize)]
pub struct ArchiverRecord {
    pub fragment_id: String,
    pub caster: String,
    pub proposal: u32,
    #[serde(deserialize_with = "deserialize_block_date_from_float")]
    pub time: BlockDate,
    pub choice: u8,
    pub raw_fragment: String,
}

use serde::de::Visitor;
use serde::Deserializer;

pub fn deserialize_block_date_from_float<'de, D>(deserializer: D) -> Result<BlockDate, D::Error>
where
    D: Deserializer<'de>,
{
    struct VoteOptionsDeserializer();

    impl<'de> Visitor<'de> for VoteOptionsDeserializer {
        type Value = BlockDate;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("float in format {epoch}.{slod_id}")
        }

        fn visit_f64<E>(self, value: f64) -> Result<BlockDate, E>
        where
            E: serde::de::Error,
        {
            Ok(value.to_string().parse().unwrap())
        }
    }

    deserializer.deserialize_f64(VoteOptionsDeserializer())
}

pub fn load_from_csv<P: AsRef<Path>>(
    csv_path: P,
) -> Result<Vec<ArchiverRecord>, ArchiveReaderError> {
    let csv_path = csv_path.as_ref();

    let mut reader = csv::ReaderBuilder::new()
        .flexible(true)
        .has_headers(true)
        .quoting(true)
        .quote(b'"')
        .trim(csv::Trim::All)
        .from_path(csv_path)?;

    let mut results = Vec::new();
    for record in reader.deserialize() {
        match record {
            Ok(data) => {
                results.push(data);
            }
            Err(e) => return Err(ArchiveReaderError::Csv(e)),
        }
    }
    Ok(results)
}

pub fn load_from_folder<P: AsRef<Path>>(
    folder_path: P,
) -> Result<Vec<ArchiverRecord>, ArchiveReaderError> {
    let mut records = Vec::new();

    for entry in std::fs::read_dir(folder_path)? {
        let entry = entry?;
        let path = entry.path();
        if let Some(extension) = path.extension().and_then(OsStr::to_str) {
            if extension == "csv" {
                records.extend(load_from_csv(path)?);
            }
        }
    }
    Ok(records)
}

#[derive(Debug, Error)]
pub enum ArchiveReaderError {
    #[error("general error")]
    General(#[from] std::io::Error),
    #[error("csv error")]
    Csv(#[from] csv::Error),
}