payback 0.6.3

Calculate to resolve debt networks with as few transactions as possible.
Documentation
use csv::ReaderBuilder;
use itertools::Itertools;
use serde_derive::Deserialize;

use crate::graph::Graph;

#[derive(Debug, PartialEq, Deserialize)]
struct NodeRecord {
    name: String,
    weight: i64,
}

#[derive(Debug, PartialEq, Deserialize)]
struct EdgeRecord {
    from: String,
    to: String,
    weight: i64,
}

impl NodeRecord {
    fn to_tuple(&self) -> (String, i64) {
        (self.name.to_owned(), self.weight)
    }
}

impl EdgeRecord {
    fn to_tuple(&self) -> ((String, String), i64) {
        ((self.from.to_owned(), self.to.to_owned()), self.weight)
    }
}

pub(crate) fn deserialize_string_to_graph(
    data: &String,
) -> Result<Graph, (csv::Error, csv::Error)> {
    let node_deserialized = deserialize_to_nodes(data)
        .map(|nodes| Into::<Graph>::into(nodes.iter().map(|n| n.to_tuple()).collect_vec()));
    if let Ok(graph) = node_deserialized {
        return Ok(graph);
    }
    let edge_deserialized = deserialize_to_edges(data)
        .map(|edges| Into::<Graph>::into(edges.iter().map(|n| n.to_tuple()).collect_vec()));
    if let Ok(graph) = edge_deserialized {
        Ok(graph)
    } else {
        Err((
            node_deserialized.unwrap_err(),
            edge_deserialized.unwrap_err(),
        ))
    }
}

fn deserialize_to_nodes(data: &String) -> Result<Vec<NodeRecord>, csv::Error> {
    let mut rdr = ReaderBuilder::new()
        .has_headers(false)
        .from_reader(data.as_bytes());
    rdr.deserialize().collect()
}

fn deserialize_to_edges(data: &String) -> Result<Vec<EdgeRecord>, csv::Error> {
    let mut rdr = ReaderBuilder::new()
        .has_headers(false)
        .from_reader(data.as_bytes());
    rdr.deserialize().collect()
}

#[cfg(test)]
mod tests {
    use env_logger::Env;
    use log::debug;

    use crate::graph_parser::{deserialize_to_edges, deserialize_to_nodes, EdgeRecord, NodeRecord};

    fn init() {
        let _ = env_logger::Builder::from_env(Env::default().default_filter_or("debug"))
            .is_test(true)
            .try_init();
    }

    #[test]
    fn test_deserialize_to_nodes() {
        init();
        debug!("Running 'test_deserialize_to_nodes'");
        let data = "A,-1\nB,2\nC,-1";
        let out = deserialize_to_nodes(&data.to_string());
        assert!(out.is_ok());
        assert_eq!(
            out.unwrap(),
            vec![
                NodeRecord {
                    name: "A".to_string(),
                    weight: -1
                },
                NodeRecord {
                    name: "B".to_string(),
                    weight: 2
                },
                NodeRecord {
                    name: "C".to_string(),
                    weight: -1
                }
            ]
        );
        let data = "A,C,1";
        assert!(deserialize_to_nodes(&data.to_string()).is_err());
    }

    #[test]
    fn test_deserialize_to_edges() {
        init();
        debug!("Running 'test_deserialize_to_edges'");
        let data = "A,B,1\nB,C,1\nC,A,1";
        let out = deserialize_to_edges(&data.to_string());
        assert!(out.is_ok());
        assert_eq!(
            out.unwrap(),
            vec![
                EdgeRecord {
                    from: "A".to_string(),
                    to: "B".to_string(),
                    weight: 1
                },
                EdgeRecord {
                    from: "B".to_string(),
                    to: "C".to_string(),
                    weight: 1
                },
                EdgeRecord {
                    from: "C".to_string(),
                    to: "A".to_string(),
                    weight: 1
                }
            ]
        );
        let data = "A,1";
        assert!(deserialize_to_edges(&data.to_string()).is_err());
    }
}