This commit is contained in:
Lewis Diamond 2021-03-14 19:41:33 -04:00
parent da582df6eb
commit d837b60327
2 changed files with 83 additions and 15 deletions

View File

@ -41,11 +41,18 @@ deposit,1,3,2.0
withdrawal,1,4,1.5 withdrawal,1,4,1.5
withdrawal,2,5,3.0"; 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::<Vec<Transaction>>().await; let txs = parse(data.as_bytes()).collect::<Vec<Transaction>>().await;
assert_eq!(expected, txs); assert_eq!(expected, txs);
}); });
} }
#[test] #[test]
fn valid_csv_with_whitespaces_is_parsed() { fn valid_csv_with_whitespaces_is_parsed() {
block_on(async { block_on(async {
@ -57,9 +64,62 @@ deposit, 1, 3, 2.0
withdrawal, 1, 4, 1.5 withdrawal, 1, 4, 1.5
withdrawal, 2, 5, 3.0"; withdrawal, 2, 5, 3.0";
let txs = parse(data.as_bytes()); let expected = vec![
let result: Vec<Transaction> = txs.collect::<Vec<Transaction>>().await; Transaction {tx_type: TransactionType::Deposit, amount: 10000, client: 1, tx: 1},
result.iter().for_each(|t| eprintln!("{:?}", t)); 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::<Vec<Transaction>>().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::<Vec<Transaction>>().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::<Vec<Transaction>>().await;
println!("PARSE: {}", "0.1".parse::<f32>().unwrap());
assert_eq!(expected, txs);
}); });
} }
} }

View File

@ -1,5 +1,6 @@
use serde::{Deserialize, Deserializer};
use serde::de; use serde::de;
use serde::{Deserialize, Deserializer};
const PRECISION: u32 = 4;
#[derive(Eq, PartialEq, Debug, Deserialize)] #[derive(Eq, PartialEq, Debug, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
@ -12,24 +13,31 @@ pub enum TransactionType {
pub struct Transaction { pub struct Transaction {
#[serde(rename = "type")] #[serde(rename = "type")]
pub tx_type: TransactionType, pub tx_type: TransactionType,
pub client: usize, pub client: u16,
pub tx: usize, pub tx: usize,
/// Amount of the smallest unit, e.g. 0.0001 as per the specification /// Amount of the smallest unit, e.g. 0.0001 as per the specification
#[serde(deserialize_with = "de_amount")] #[serde(deserialize_with = "de_amount")]
pub amount: usize, pub amount: usize,
} }
fn de_amount<'de, D>(deserializer: D) -> Result<usize, D::Error> where D: Deserializer<'de> { fn de_amount<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: Deserializer<'de>,
{
//TODO validate for input such as `.100` so it doesn't give 100 //TODO validate for input such as `.100` so it doesn't give 100
let deserialized = String::deserialize(deserializer)?; let deserialized = String::deserialize(deserializer)?;
let mut splitted = deserialized.split('.'); let mut splitted = deserialized.split('.');
let mut ret: usize = 0; let units = splitted
let mut factor = 4; .next()
while let Some(s) = splitted.next() { .map_or(Ok(0usize), |v| v.parse::<usize>())
ret += s.parse::<usize>().map_err(de::Error::custom)? * 10usize.pow(factor); .map_err(de::Error::custom)?
if factor < 1 { break;} .checked_mul(10usize.pow(PRECISION))
factor -= 4; .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::<usize>())
.map_err(de::Error::custom)?;
Ok(ret) Ok(units + dec)
} }