use self::Items::*;
use chrono::{DateTime, FixedOffset};
use std::clone::Clone;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
#[derive(Eq, PartialEq, Hash)]
pub enum Items {
Timestamp,
Exchange,
SourceCurrency,
DestinationCurrency,
ForwardFactor,
BackwardFactor,
}
impl Items {
pub fn get_label(&self) -> String {
match self {
Timestamp => "timestamp".to_string(),
Exchange => "exchange".to_string(),
SourceCurrency => "source_currency".to_string(),
DestinationCurrency => "destination_currency".to_string(),
ForwardFactor => "forward_factor".to_string(),
BackwardFactor => "backward_factor".to_string(),
}
}
}
impl fmt::Display for Items {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get_label())
}
}
pub struct PriceUpdate<N, E> {
timestamp: DateTime<FixedOffset>,
exchange: N,
source_currency: N,
destination_currency: N,
forward_factor: E,
backward_factor: E,
}
impl<N, E> PriceUpdate<N, E>
where
N: Clone + FromStr,
<N as FromStr>::Err: fmt::Debug,
E: FromStr,
<E as FromStr>::Err: fmt::Debug,
{
pub fn new(
timestamp: DateTime<FixedOffset>,
exchange: N,
source_currency: N,
destination_currency: N,
forward_factor: E,
backward_factor: E,
) -> Self {
Self {
timestamp,
exchange,
source_currency,
destination_currency,
forward_factor,
backward_factor,
}
}
pub fn get_index(&self) -> (N, N, N) {
(
self.exchange.clone(),
self.source_currency.clone(),
self.destination_currency.clone(),
)
}
pub fn get_timestamp(&self) -> &DateTime<FixedOffset> {
&self.timestamp
}
pub fn get_exchange(&self) -> &N {
&self.exchange
}
pub fn get_source_currency(&self) -> &N {
&self.source_currency
}
pub fn get_destination_currency(&self) -> &N {
&self.destination_currency
}
pub fn get_forward_factor(&self) -> &E {
&self.forward_factor
}
pub fn get_backward_factor(&self) -> &E {
&self.backward_factor
}
pub fn parse_line(line: &str) -> Result<PriceUpdate<N, E>, Vec<String>> {
let mut iter = line.split_whitespace();
let mut values = HashMap::new();
let mut errors: Vec<String> = Vec::new();
for item in &[
Timestamp,
Exchange,
SourceCurrency,
DestinationCurrency,
ForwardFactor,
BackwardFactor,
] {
let value: Option<&str> = iter.next();
match value {
Some(s) => {
values.insert(item, s);
}
None => {
errors.push(format!("The line item <{}> is missing!", item));
}
}
}
if !errors.is_empty() {
return Err(errors);
}
let timestamp = DateTime::parse_from_rfc3339(values[&Timestamp]);
if timestamp.is_err() {
errors.push(format!(
"The line item <{}> can not be parsed (wrong format)!",
&Timestamp
));
}
let forward_factor = values[&ForwardFactor].parse::<E>();
if forward_factor.is_err() {
errors.push(format!(
"The line item <{}> can not be parsed (wrong format)!",
&ForwardFactor
));
}
let backward_factor = values[&BackwardFactor].parse::<E>();
if backward_factor.is_err() {
errors.push(format!(
"The line item <{}> can not be parsed (wrong format)!",
&BackwardFactor
));
}
let exchange = values[&Exchange].to_uppercase().parse::<N>();
if exchange.is_err() {
errors.push(format!(
"The line item <{}> can not be parsed (wrong format)!",
&Exchange
));
}
let source_currency = values[&SourceCurrency].to_uppercase().parse::<N>();
if source_currency.is_err() {
errors.push(format!(
"The line item <{}> can not be parsed (wrong format)!",
&SourceCurrency
));
}
let destination_currency = values[&DestinationCurrency].to_uppercase().parse::<N>();
if destination_currency.is_err() {
errors.push(format!(
"The line item <{}> can not be parsed (wrong format)!",
&DestinationCurrency
));
}
if !errors.is_empty() {
return Err(errors);
}
Ok(Self::new(
timestamp.unwrap(),
exchange.unwrap(),
source_currency.unwrap(),
destination_currency.unwrap(),
forward_factor.unwrap(),
backward_factor.unwrap(),
))
}
}
#[cfg(test)]
mod tests {
use crate::request::price_update::Items::*;
use crate::request::price_update::PriceUpdate;
#[test]
fn parse_line() {
let line = "2017-11-01T09:42:23+00:00 KRAKEN BTC USD 1000.0 0.0009";
let price_update = PriceUpdate::<String, f32>::parse_line(&line.to_string());
assert!(price_update.is_ok());
let price_update = price_update.unwrap();
assert_eq!(
price_update.timestamp.to_rfc3339(),
String::from("2017-11-01T09:42:23+00:00")
);
assert_eq!(price_update.exchange, "KRAKEN");
assert_eq!(price_update.source_currency, "BTC");
assert_eq!(price_update.destination_currency, "USD");
assert_eq!(price_update.forward_factor, 1000.0);
assert_eq!(price_update.backward_factor, 0.0009);
}
#[test]
fn parse_line_with_missing_values() {
let line = "";
let price_update = PriceUpdate::<String, f32>::parse_line(&line.to_string());
assert!(price_update.is_err());
let mut errors = price_update.err().unwrap();
assert_eq!(
errors.pop().unwrap(),
format!("The line item <{}> is missing!", BackwardFactor)
);
assert_eq!(
errors.pop().unwrap(),
format!("The line item <{}> is missing!", ForwardFactor)
);
assert_eq!(
errors.pop().unwrap(),
format!("The line item <{}> is missing!", DestinationCurrency)
);
assert_eq!(
errors.pop().unwrap(),
format!("The line item <{}> is missing!", SourceCurrency)
);
assert_eq!(
errors.pop().unwrap(),
format!("The line item <{}> is missing!", Exchange)
);
assert_eq!(
errors.pop().unwrap(),
format!("The line item <{}> is missing!", Timestamp)
);
assert!(errors.pop().is_none());
}
#[test]
fn parse_line_with_parse_errors() {
let line = String::from(
"201--11-01T09:42:23+00:00 KRAKEN BTC USD thousand zero-point-something-small",
);
let price_update = PriceUpdate::<String, f32>::parse_line(&line);
assert!(price_update.is_err());
let mut errors = price_update.err().unwrap();
assert_eq!(
errors.pop().unwrap(),
format!(
"The line item <{}> can not be parsed (wrong format)!",
BackwardFactor
)
);
assert_eq!(
errors.pop().unwrap(),
format!(
"The line item <{}> can not be parsed (wrong format)!",
ForwardFactor
)
);
assert_eq!(
errors.pop().unwrap(),
format!(
"The line item <{}> can not be parsed (wrong format)!",
Timestamp
)
);
assert!(errors.pop().is_none());
}
}