diff --git a/src/lib.rs b/src/lib.rs index 3d1c089..1322eef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,11 +41,18 @@ deposit,1,3,2.0 withdrawal,1,4,1.5 withdrawal,2,5,3.0"; - let expected = vec![Transaction {tx_type: TransactionType::Deposit, amount: 10000, client: 1, tx: 1}]; + let expected = vec![ + Transaction {tx_type: TransactionType::Deposit, amount: 10000, client: 1, tx: 1}, + Transaction {tx_type: TransactionType::Deposit, amount: 20000, client: 2, tx: 2}, + Transaction {tx_type: TransactionType::Deposit, amount: 20000, client: 1, tx: 3}, + Transaction {tx_type: TransactionType::Withdrawal, amount: 15000, client: 1, tx: 4}, + Transaction {tx_type: TransactionType::Withdrawal, amount: 30000, client: 2, tx: 5}, + ]; let txs = parse(data.as_bytes()).collect::>().await; assert_eq!(expected, txs); }); } + #[test] fn valid_csv_with_whitespaces_is_parsed() { block_on(async { @@ -57,9 +64,62 @@ deposit, 1, 3, 2.0 withdrawal, 1, 4, 1.5 withdrawal, 2, 5, 3.0"; - let txs = parse(data.as_bytes()); - let result: Vec = txs.collect::>().await; - result.iter().for_each(|t| eprintln!("{:?}", t)); + let expected = vec![ + Transaction {tx_type: TransactionType::Deposit, amount: 10000, client: 1, tx: 1}, + Transaction {tx_type: TransactionType::Deposit, amount: 20000, client: 2, tx: 2}, + Transaction {tx_type: TransactionType::Deposit, amount: 20000, client: 1, tx: 3}, + Transaction {tx_type: TransactionType::Withdrawal, amount: 15000, client: 1, tx: 4}, + Transaction {tx_type: TransactionType::Withdrawal, amount: 30000, client: 2, tx: 5}, + ]; + let txs = parse(data.as_bytes()).collect::>().await; + assert_eq!(expected, txs); + }); + } + + #[test] + fn amounts_are_parsed_correctly() { + block_on(async { + let data = "\ +type,client,tx,amount +deposit,1,1,1.0001 +deposit,2,2,2.0010 +deposit,1,3,10.01 +withdrawal,1,4,01.10 +withdrawal,2,5,10.0110101"; + + let expected = vec![ + Transaction {tx_type: TransactionType::Deposit, amount: 10001, client: 1, tx: 1}, + Transaction {tx_type: TransactionType::Deposit, amount: 20010, client: 2, tx: 2}, + Transaction {tx_type: TransactionType::Deposit, amount: 100100, client: 1, tx: 3}, + Transaction {tx_type: TransactionType::Withdrawal, amount: 11000, client: 1, tx: 4}, + Transaction {tx_type: TransactionType::Withdrawal, amount: 100110, client: 2, tx: 5}, + ]; + let txs = parse(data.as_bytes()).collect::>().await; + assert_eq!(expected, txs); + }); + } + + #[test] + fn invalid_amoutns_are_filtered() { + block_on(async { + let data = "\ +type,client,tx,amount +deposit,1,1,99999999999999999 +deposit,2,2,18446744073709551615 +deposit,1,3,18446744073709551616 +withdrawal,1,4,0 +withdrawal,2,5,-1 +withdrawal,1,6,-99999999999999999 +withdrawal,1,6,-18446744073709551615 +withdrawal,1,7,-18446744073709551616"; + + let expected = vec![ + Transaction {tx_type: TransactionType::Withdrawal, amount: 0, client: 1, tx: 4}, + ]; + let txs = parse(data.as_bytes()).collect::>().await; + println!("PARSE: {}", "0.1".parse::().unwrap()); + + assert_eq!(expected, txs); }); } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 895ce1e..908f08f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,6 @@ -use serde::{Deserialize, Deserializer}; use serde::de; +use serde::{Deserialize, Deserializer}; +const PRECISION: u32 = 4; #[derive(Eq, PartialEq, Debug, Deserialize)] #[serde(rename_all = "lowercase")] @@ -12,24 +13,31 @@ pub enum TransactionType { pub struct Transaction { #[serde(rename = "type")] pub tx_type: TransactionType, - pub client: usize, + pub client: u16, pub tx: usize, /// Amount of the smallest unit, e.g. 0.0001 as per the specification #[serde(deserialize_with = "de_amount")] pub amount: usize, } -fn de_amount<'de, D>(deserializer: D) -> Result where D: Deserializer<'de> { +fn de_amount<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ //TODO validate for input such as `.100` so it doesn't give 100 let deserialized = String::deserialize(deserializer)?; let mut splitted = deserialized.split('.'); - let mut ret: usize = 0; - let mut factor = 4; - while let Some(s) = splitted.next() { - ret += s.parse::().map_err(de::Error::custom)? * 10usize.pow(factor); - if factor < 1 { break;} - factor -= 4; - }; + let units = splitted + .next() + .map_or(Ok(0usize), |v| v.parse::()) + .map_err(de::Error::custom)? + .checked_mul(10usize.pow(PRECISION)) + .ok_or_else(|| de::Error::custom("Value too large"))?; + //TODO improve this to avoid `format!` + let dec = splitted + .next() + .map_or(Ok(0usize), |v| format!("{:0<4.4}", v).parse::()) + .map_err(de::Error::custom)?; - Ok(ret) + Ok(units + dec) }