Return Result from CSP parse_reader, better error messages

This commit is contained in:
RunasSudo 2021-10-17 17:00:24 +11:00
parent 71dc671c34
commit e78d06289a
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 22 additions and 13 deletions

7
Cargo.lock generated
View File

@ -28,6 +28,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anyhow"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.6" version = "0.3.6"
@ -711,6 +717,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
name = "opentally" name = "opentally"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"assert_cmd", "assert_cmd",
"clap", "clap",
"console_error_panic_hook", "console_error_panic_hook",

View File

@ -8,6 +8,7 @@ edition = "2018"
crate-type = ["lib", "cdylib"] crate-type = ["lib", "cdylib"]
[dependencies] [dependencies]
anyhow = "1.0.44"
csv = "1.1.6" csv = "1.1.6"
derive_builder = "0.10.2" derive_builder = "0.10.2"
derive_more = "0.99.14" derive_more = "0.99.14"

View File

@ -113,7 +113,7 @@ pub fn main(mut cmd_opts: SubcmdOptions) -> Result<(), i32> {
} }
"csp" => { "csp" => {
let file = File::open(cmd_opts.infile).expect("IO Error"); let file = File::open(cmd_opts.infile).expect("IO Error");
election = parser::csp::parse_reader(file, cmd_opts.require_1, cmd_opts.require_sequential, cmd_opts.require_strict_order); election = parser::csp::parse_reader(file, cmd_opts.require_1, cmd_opts.require_sequential, cmd_opts.require_strict_order).expect("Syntax Error");
} }
_ => unreachable!() _ => unreachable!()
}; };

View File

@ -18,6 +18,7 @@
use crate::election::{Ballot, Candidate, Election}; use crate::election::{Ballot, Candidate, Election};
use crate::numbers::Number; use crate::numbers::Number;
use anyhow::{Context, Result};
use csv::ReaderBuilder; use csv::ReaderBuilder;
use itertools::Itertools; use itertools::Itertools;
@ -25,7 +26,7 @@ use std::collections::HashMap;
use std::io::Read; use std::io::Read;
/// Parse the given CSP file /// Parse the given CSP file
pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequential: bool, require_strict_order: bool) -> Election<N> { pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequential: bool, require_strict_order: bool) -> Result<Election<N>> {
// Read CSV file // Read CSV file
let mut reader = ReaderBuilder::new() let mut reader = ReaderBuilder::new()
.has_headers(true) .has_headers(true)
@ -33,14 +34,14 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
// Read candidates // Read candidates
let mut candidates = Vec::new(); let mut candidates = Vec::new();
let mut idx_map = HashMap::new(); // Map csp index -> candidates index let mut col_map = HashMap::new(); // Map csp column -> candidates index
for (i, cand_name) in reader.headers().expect("Syntax Error").into_iter().enumerate() { for (i, cand_name) in reader.headers()?.into_iter().enumerate() {
if cand_name == "$mult" { if cand_name == "$mult" {
continue; continue;
} }
idx_map.insert(i, candidates.len()); col_map.insert(i, candidates.len());
candidates.push(Candidate { candidates.push(Candidate {
name: cand_name.to_string(), name: cand_name.to_string(),
}); });
@ -49,22 +50,22 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
// Read ballots // Read ballots
let mut ballots = Vec::new(); let mut ballots = Vec::new();
for record in reader.into_records() { for (csv_row, record) in reader.into_records().enumerate() {
let record = record.expect("Syntax Error"); let record = record?;
let mut value = N::one(); let mut value = N::one();
// Record preferences // Record preferences
let mut preferences = Vec::new(); // Vec of (ranking, candidate index) let mut preferences = Vec::new(); // Vec of (ranking, candidate index)
for (csv_index, preference) in record.into_iter().enumerate() { for (csv_col, preference) in record.into_iter().enumerate() {
match idx_map.get(&csv_index) { match col_map.get(&csv_col) {
Some(cand_index) => { Some(cand_index) => {
// Preference // Preference
if preference.len() == 0 || preference == "-" { if preference.len() == 0 || preference == "-" {
continue; continue;
} }
let preference: usize = preference.parse().expect("Syntax Error"); let preference: usize = preference.parse().context(format!("Invalid number \"{}\" at row {}, column {}", preference, csv_row + 2, csv_col + 1))?;
if preference == 0 { if preference == 0 {
continue; continue;
} }
@ -73,7 +74,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
} }
None => { None => {
// $mult column // $mult column
let mult: usize = preference.parse().expect("Syntax Error"); let mult: usize = preference.parse().context(format!("Invalid number \"{}\" at row {}, column {}", preference, csv_row + 2, csv_col + 1))?;
if mult == 1 { if mult == 1 {
continue; continue;
} }
@ -131,7 +132,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
}); });
} }
return Election { return Ok(Election {
name: String::new(), name: String::new(),
seats: 0, seats: 0,
candidates: candidates, candidates: candidates,
@ -139,5 +140,5 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
ballots: ballots, ballots: ballots,
total_votes: None, total_votes: None,
constraints: None, constraints: None,
}; });
} }