From 4faab11ed3413f98f1758e1558bcf7edc942ec2a Mon Sep 17 00:00:00 2001 From: scoobybejesus Date: Fri, 27 Nov 2020 11:50:45 -0500 Subject: [PATCH] Error handling. Added user warning and exit()'s in place of asserts. Related formatting. --- Cargo.toml | 2 +- src/crptls_lib/create_lots_mvmts.rs | 21 +++++++++-- src/crptls_lib/csv_import_accts_txns.rs | 46 +++++++++++-------------- src/crptls_lib/transaction.rs | 32 ++++++++++++----- src/setup.rs | 5 ++- 5 files changed, 68 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80f7712..6774110 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptools" -version = "0.9.1" +version = "0.9.2" authors = ["scoobybejesus "] edition = "2018" description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'." diff --git a/src/crptls_lib/create_lots_mvmts.rs b/src/crptls_lib/create_lots_mvmts.rs index 3724fc4..9f074d9 100644 --- a/src/crptls_lib/create_lots_mvmts.rs +++ b/src/crptls_lib/create_lots_mvmts.rs @@ -242,6 +242,12 @@ pub(crate) fn create_lots_and_movements( wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &chosen_home_currency, &raw_acct_map, &acct_map); continue } else { + + if acct.list_of_lots.borrow().len() == 0 { + println!("FATAL: There are zero lots to spend from in transaction:\n{:#?}",txn); + std::process::exit(1); + } + let list_of_lots_to_use = acct.list_of_lots.clone(); // the following returns vec to be iterated from beginning to end, which provides the index for the correct lot @@ -557,7 +563,8 @@ pub(crate) fn create_lots_and_movements( } TxType::ToSelf => { if raw_acct.is_margin { - { println!("\n Found margin actionrecord in toself txn # {} \n", txn.tx_number); use std::process::exit; exit(1) }; + println!("FATAL: Consult developer. Found margin actionrecord in ToSelf transaction:\n{:#?}", txn); + std::process::exit(1); } else { process_multiple_incoming_lots_and_mvmts( txn_num, @@ -729,7 +736,7 @@ fn fit_into_lots( &chosen_home_currency, &ar_map, &raw_acct_map, - &acct_map + &acct_map, ); return; } @@ -777,6 +784,14 @@ fn fit_into_lots( wrap_mvmt_and_push(mvmt_that_fits_in_lot, &spawning_ar, &lot, &chosen_home_currency, &raw_acct_map, &acct_map); let remainder_amt_to_recurse = remainder_amt + sum_of_mvmts_in_lot; // println!("Remainder amount to recurse: {}", remainder_amt_to_recurse); + + if vec_of_ordered_index_values.len() == current_index_position + 1 { + println!("FATAL: Txn {} on {} spending {} {} has run out of lots to spend from.", + txn_num, lot.date_as_string, ar.amount, raw_acct.ticker); + println!("Account balance is only: {}", acct.get_sum_of_amts_in_lots()); + std::process::exit(1); + } + current_index_position += 1; let lot_index = vec_of_ordered_index_values[current_index_position]; let newly_chosen_lot = list_of_lots_to_use.borrow()[lot_index].clone(); @@ -806,7 +821,7 @@ fn fit_into_lots( &chosen_home_currency, &ar_map, &raw_acct_map, - &acct_map + &acct_map, ); } diff --git a/src/crptls_lib/csv_import_accts_txns.rs b/src/crptls_lib/csv_import_accts_txns.rs index 5720697..4d09bb2 100644 --- a/src/crptls_lib/csv_import_accts_txns.rs +++ b/src/crptls_lib/csv_import_accts_txns.rs @@ -55,14 +55,7 @@ fn import_accounts( let mut header3: Option = None; // ticker let header4: csv::StringRecord; // is_margin - // A StringRecord doesn't accept the same range indexing we need below, so we create our own - let mut headerstrings: Vec = Vec::with_capacity(header1.len()); - - for element in header1.into_iter() { - headerstrings.push(element.to_string()) - } - - // Account Creation loop. We set hasheaders() to true above, so the first record here is the second row of the CSV + // Account Creation loop. With rdr.has_headers() set to true above, the first record here is the second row of the CSV for result in rdr.records() { // This initial iteration through records will break after the 4th row, after accounts have been created let record = result?; @@ -78,32 +71,32 @@ fn import_accounts( header4 = record.clone(); // println!("Assigned last header, record: {:?}", record); - let warn = "FATAL: Transactions will not import correctly if account numbers in the CSV import file aren't + // A StringRecord doesn't accept the same range indexing needed below, so a Vec of Strings will be used + let mut headerstrings: Vec = Vec::with_capacity(header1.len()); + for field in header1.into_iter() { + headerstrings.push(field.to_string()) + } + + let acct_num_warn = "Transactions will not import correctly if account numbers in the CSV import file aren't ordered chronologically (i.e., beginning in column 4 - the 1st account column - the value should be 1. The next column's value should be 2, then 3, etc, until the final account)."; - // We've got all our header rows. It's now that we set up the accounts. + // Header row variables have been set. It's now time to set up the accounts. println!("Attempting to create accounts..."); - let mut no_dup_acct_nums = HashMap::new(); let length = &headerstrings.len(); - for num in headerstrings[3..*length].iter().enumerate() { - let counter = no_dup_acct_nums.entry(num).or_insert(0); - *counter += 1; - } + for (idx, field) in headerstrings[3..*length].iter().enumerate() { - for acct_num in no_dup_acct_nums.keys() { - assert_eq!(no_dup_acct_nums[acct_num], 1, "Found accounts with duplicate numbers during import."); - } + // Parse account numbers. + let account_num = field.parse::().expect("Header row account number should parse into u16."); + // For now, their columns aren't remembered. Instead, they must have a particular index. 0th idx is the 1st account, and so on. + if account_num != ((idx + 1) as u16) { + println!("FATAL: CSV Import: {}", acct_num_warn); + std::process::exit(1); + } - for (idx, item) in headerstrings[3..*length].iter().enumerate() { - - // println!("Headerstrings value: {:?}", item); let ind = idx+3; // Add three because the idx skips the first three 'key' columns - let account_num = item.parse::()?; - assert_eq!((idx + 1) as u16, account_num, "Found improper Account Number usage: {}", warn); - let name:String = header2.clone().unwrap()[ind].trim().to_string(); let ticker:String = header3.clone().unwrap()[ind].trim().to_string(); // no .to_uppercase() b/c margin... let margin_string = &header4.clone()[ind]; @@ -111,7 +104,10 @@ The next column's value should be 2, then 3, etc, until the final account)."; let is_margin:bool = match margin_string.trim().to_lowercase().as_str() { "no" | "non" | "false" => false, "yes" | "margin" | "true" => true, - _ => { println!("\n Couldn't parse margin value for acct {} {} \n",account_num, name); process::exit(1) } + _ => { + println!("\n FATAL: CSV Import: Couldn't parse margin value for account {} {} \n",account_num, name); + process::exit(1) + } }; let just_account: RawAccount = RawAccount { diff --git a/src/crptls_lib/transaction.rs b/src/crptls_lib/transaction.rs index 92226e1..8111c6a 100644 --- a/src/crptls_lib/transaction.rs +++ b/src/crptls_lib/transaction.rs @@ -52,7 +52,8 @@ impl Transaction { let ar2_ticker = ar2_ticker_comp[0]; if first_ar.direction() == second_ar.direction() { - println!("Program exiting. Found transaction with two actionRecords with the same polarity: {:?}", self); process::exit(1); + println!("FATAL: TxType: Found transaction with two actionRecords with the same polarity: \n{:#?}", self); + process::exit(1); } if ar1_ticker == ar2_ticker { if ar1_raw_acct.is_margin != ar2_raw_acct.is_margin { @@ -67,10 +68,12 @@ impl Transaction { } } else if self.action_record_idx_vec.len() > 2 { - println!("Program exiting. Found transaction with too many actionRecords: {:?}", self); process::exit(1); + println!("FATAL: TxType: Found transaction with too many actionRecords: \n{:#?}", self); + process::exit(1); } else { - println!("Program exiting. Found transaction with no actionRecords: {:?}", self); process::exit(1); + println!("FATAL: TxType: Found transaction with no actionRecords: \n{:#?}", self); + process::exit(1); } } @@ -92,11 +95,23 @@ impl Transaction { } } else { - assert_eq!(self.action_record_idx_vec.len(),2, - "Each txn can only have one or two ARs. Txn has {} ARs.", self.action_record_idx_vec.len()); + if self.action_record_idx_vec.len() != 2 { + println!("FATAL: Each transaction may have up to two actionrecords, and there are {} actionrecords in transaction:\n{:#?}", + self.action_record_idx_vec.len(), self); + } - let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap(); - let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap(); + let first_ar = match ars.get(&self.action_record_idx_vec[0]) { + Some(x) => x, + None => { + println!("FATAL: ActionRecord not found for: \n{:#?}", self); + process::exit(1)} + }; + let second_ar = match ars.get(&self.action_record_idx_vec[1]) { + Some(x) => x, + None => { + println!("FATAL: ActionRecord not found for: \n{:#?}", self); + process::exit(1)} + }; let first_acct = acct_map.get(&first_ar.account_key).unwrap(); let second_acct = acct_map.get(&second_ar.account_key).unwrap(); @@ -144,7 +159,8 @@ impl Transaction { quote = second_acct_raw_key; Ok((base, quote)) } else { - println!("{}", VariousErrors::MarginNoUnderbar); use std::process::exit; exit(1) + println!("FATAL: {}", VariousErrors::MarginNoUnderbar); + std::process::exit(1); } } diff --git a/src/setup.rs b/src/setup.rs index 52e619e..701ede2 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -94,7 +94,10 @@ pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathB "h" => { "-" } "s" => { "/" } "p" => { "." } - _ => { println!("\nFATAL: The date-separator arg requires either an 'h', an 's', or a 'p'.\n"); process::exit(1) } + _ => { + println!("\nFATAL: ENV: The date-separator arg requires either an 'h', an 's', or a 'p'.\n"); + process::exit(1) + } }; let input_file_path = match cmd_args.file_to_import {