|
@@ -6,7 +6,7 @@ use std::cmp::{PartialEq, Eq}; |
|
|
use std::convert::TryFrom; |
|
|
use std::convert::TryFrom; |
|
|
|
|
|
|
|
|
use serde::{Serialize, Deserialize}; |
|
|
use serde::{Serialize, Deserialize}; |
|
|
use serde::de::{self, Deserializer}; |
|
|
|
|
|
|
|
|
use serde::de::{self, Deserializer, Visitor}; |
|
|
use serde::Serializer; |
|
|
use serde::Serializer; |
|
|
//use decimal::d128; |
|
|
//use decimal::d128; |
|
|
//use chrono::{DateTime, Utc, TimeZone}; |
|
|
//use chrono::{DateTime, Utc, TimeZone}; |
|
@@ -139,7 +139,9 @@ impl FromStr for Side { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
macro_rules! make_currency { |
|
|
macro_rules! make_currency { |
|
|
($(|$ticker:ident, $name:expr, $code:expr, $upper:expr),*) => { |
|
|
|
|
|
|
|
|
//(@as_ident $name:ident) => { $name }; |
|
|
|
|
|
|
|
|
|
|
|
($(|$ticker:ident, $name:expr, $code:expr, $upper:ident),*) => { |
|
|
#[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord, Serialize)] |
|
|
#[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord, Serialize)] |
|
|
#[allow(non_camel_case_types)] |
|
|
#[allow(non_camel_case_types)] |
|
|
#[repr(u8)] |
|
|
#[repr(u8)] |
|
@@ -172,6 +174,35 @@ macro_rules! make_currency { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[allow(non_upper_case_globals)] |
|
|
|
|
|
pub fn from_bytes<'a>(bytes: &'a [u8]) -> Result<Self, UnlistedCurrency<'a>> { |
|
|
|
|
|
// this declares for each currency the equivalent of |
|
|
|
|
|
// const BTC : &[u8] = "btc".as_bytes(); |
|
|
|
|
|
$( |
|
|
|
|
|
const $upper: &[u8] = stringify!($ticker).as_bytes(); |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
// this declares for each currency the equivalent of |
|
|
|
|
|
// const btc : &[u8] = "BTC".as_bytes(); |
|
|
|
|
|
$( |
|
|
|
|
|
const $ticker: &[u8] = stringify!($upper).as_bytes(); |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
match bytes { |
|
|
|
|
|
// first try lowercase (identified by uppercase consts) |
|
|
|
|
|
$( |
|
|
|
|
|
$upper => { return Ok(Currency::$ticker) } |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
// then try uppercase (identified by lowercase consts) |
|
|
|
|
|
$( |
|
|
|
|
|
$ticker => { return Ok(Currency::$ticker) } |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
other => Err(UnlistedCurrency(std::str::from_utf8(other).unwrap_or("<utf8 error>"))) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/// Optimized for expecting lowercase. Does not attempt to search beyond |
|
|
/// Optimized for expecting lowercase. Does not attempt to search beyond |
|
|
/// every currency symbol in lowercase. |
|
|
/// every currency symbol in lowercase. |
|
|
/// |
|
|
/// |
|
@@ -205,6 +236,14 @@ macro_rules! make_currency { |
|
|
$( c!($ticker) ),* |
|
|
$( c!($ticker) ),* |
|
|
] |
|
|
] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn variant_str_slice() -> &'static [&'static str] { |
|
|
|
|
|
&[ |
|
|
|
|
|
$( |
|
|
|
|
|
stringify!($ticker) |
|
|
|
|
|
),* |
|
|
|
|
|
] |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
impl TryFrom<u8> for Currency { |
|
|
impl TryFrom<u8> for Currency { |
|
@@ -256,6 +295,44 @@ macro_rules! make_currency { |
|
|
assert_eq!(c.as_str().to_uppercase(), c.to_str_uppercase().to_string()); |
|
|
assert_eq!(c.as_str().to_uppercase(), c.to_str_uppercase().to_string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn checks_from_bytes_for_lower_and_upper_tickers() { |
|
|
|
|
|
$( |
|
|
|
|
|
assert_eq!( |
|
|
|
|
|
Currency::from_bytes(Currency::$ticker.as_str().as_bytes()).unwrap(), |
|
|
|
|
|
Currency::$ticker |
|
|
|
|
|
); |
|
|
|
|
|
assert_eq!( |
|
|
|
|
|
Currency::from_bytes(Currency::$ticker.to_str_uppercase().as_bytes()).unwrap(), |
|
|
|
|
|
Currency::$ticker |
|
|
|
|
|
); |
|
|
|
|
|
)* |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn check_serde_json_deser() { |
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)] |
|
|
|
|
|
struct A { |
|
|
|
|
|
pub denom: Currency, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$( |
|
|
|
|
|
{ |
|
|
|
|
|
let a = A { denom: Currency::$ticker }; |
|
|
|
|
|
let to_json = serde_json::to_string(&a).unwrap(); |
|
|
|
|
|
let manual_json = format!("{{\"denom\":\"{}\"}}", stringify!($ticker)); |
|
|
|
|
|
let manual_json_uppercase = format!("{{\"denom\":\"{}\"}}", stringify!($upper)); |
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&to_json).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&manual_json).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap()); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
)* |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@@ -393,13 +470,223 @@ fn check_generated_currency_fns() { |
|
|
assert_eq!(i16::from(c!(usd)), 100_i16); |
|
|
assert_eq!(i16::from(c!(usd)), 100_i16); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
struct CurrencyVisitor; |
|
|
|
|
|
struct ExchangeVisitor; |
|
|
|
|
|
struct TickerVisitor; |
|
|
|
|
|
|
|
|
|
|
|
impl<'de> Visitor<'de> for CurrencyVisitor { |
|
|
|
|
|
type Value = Currency; |
|
|
|
|
|
|
|
|
|
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { |
|
|
|
|
|
formatter.write_str("a currency symbol or ticker, like those used by exchanges (e.g. btc, usd)") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Currency::from_str_lowercase(v) |
|
|
|
|
|
.or_else(|_| Currency::from_str_uppercase(v)) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Currency::from_str_lowercase(v) |
|
|
|
|
|
.or_else(|_| Currency::from_str_uppercase(v)) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Currency::from_bytes(v) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Currency::from_bytes(v) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl<'de> Visitor<'de> for ExchangeVisitor { |
|
|
|
|
|
type Value = Exchange; |
|
|
|
|
|
|
|
|
|
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { |
|
|
|
|
|
formatter.write_str("a four-character exchange symbol (e.g. gdax, bmex)") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Exchange::from_str_lowercase(v) |
|
|
|
|
|
.or_else(|_| Exchange::from_str_uppercase(v)) |
|
|
|
|
|
.map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Exchange::from_str_lowercase(v) |
|
|
|
|
|
.or_else(|_| Exchange::from_str_uppercase(v)) |
|
|
|
|
|
.map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Exchange::from_bytes(v) |
|
|
|
|
|
.map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
Exchange::from_bytes(v) |
|
|
|
|
|
.map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice())) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl<'de> Visitor<'de> for TickerVisitor { |
|
|
|
|
|
type Value = Ticker; |
|
|
|
|
|
|
|
|
|
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { |
|
|
|
|
|
formatter.write_str("a currency rate symbol or ticker, like those used by exchanges (e.g. btc_usd)") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
let mut parts = v.split(|c| c == '_' || c == '-' || c == '/'); |
|
|
|
|
|
|
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(base_str) => { |
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(quote_str) => { |
|
|
|
|
|
let base = Currency::from_str_lowercase(base_str) |
|
|
|
|
|
.or_else(|_| Currency::from_str_uppercase(base_str)) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
let quote = Currency::from_str_lowercase(quote_str) |
|
|
|
|
|
.or_else(|_| Currency::from_str_uppercase(quote_str)) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(Ticker { base, quote }) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("quote")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("base")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
let mut parts = v.split(|c| c == '_' || c == '-' || c == '/'); |
|
|
|
|
|
|
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(base_str) => { |
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(quote_str) => { |
|
|
|
|
|
let base = Currency::from_str_lowercase(base_str) |
|
|
|
|
|
.or_else(|_| Currency::from_str_uppercase(base_str)) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
let quote = Currency::from_str_lowercase(quote_str) |
|
|
|
|
|
.or_else(|_| Currency::from_str_uppercase(quote_str)) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(Ticker { base, quote }) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("quote")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("base")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
let mut parts = v.split(|&c| c == b'_' || c == b'-' || c == b'/'); |
|
|
|
|
|
|
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(base_str) => { |
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(quote_str) => { |
|
|
|
|
|
let base = Currency::from_bytes(base_str) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
let quote = Currency::from_bytes(quote_str) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(Ticker { base, quote }) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("quote")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("base")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> where |
|
|
|
|
|
E: de::Error |
|
|
|
|
|
{ |
|
|
|
|
|
let mut parts = v.split(|&c| c == b'_' || c == b'-' || c == b'/'); |
|
|
|
|
|
|
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(base_str) => { |
|
|
|
|
|
match parts.next() { |
|
|
|
|
|
Some(quote_str) => { |
|
|
|
|
|
let base = Currency::from_bytes(base_str) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
let quote = Currency::from_bytes(quote_str) |
|
|
|
|
|
.map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(Ticker { base, quote }) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("quote")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
None => Err(E::missing_field("base")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
impl <'de> Deserialize<'de> for Currency { |
|
|
impl <'de> Deserialize<'de> for Currency { |
|
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> |
|
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> |
|
|
where D: Deserializer<'de> { |
|
|
|
|
|
|
|
|
where D: Deserializer<'de> |
|
|
|
|
|
{ |
|
|
|
|
|
deserializer.deserialize_bytes(CurrencyVisitor) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
use serde::de::Error; |
|
|
|
|
|
let c = String::deserialize(deserializer)?; |
|
|
|
|
|
Currency::from_str(&c).map_err(|err| D::Error::custom(format!("{:?}", err))) |
|
|
|
|
|
|
|
|
impl <'de> Deserialize<'de> for Exchange { |
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> |
|
|
|
|
|
where D: Deserializer<'de> |
|
|
|
|
|
{ |
|
|
|
|
|
deserializer.deserialize_bytes(ExchangeVisitor) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl <'de> Deserialize<'de> for Ticker { |
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> |
|
|
|
|
|
where D: Deserializer<'de> |
|
|
|
|
|
{ |
|
|
|
|
|
deserializer.deserialize_bytes(TickerVisitor) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@@ -410,9 +697,9 @@ impl Display for Currency { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
macro_rules! make_exchange { |
|
|
macro_rules! make_exchange { |
|
|
($(|$ticker:ident, $name:expr, $code:expr),*) => { |
|
|
|
|
|
|
|
|
($(|$ticker:ident, $name:expr, $code:expr, $upper:ident),*) => { |
|
|
#[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord, |
|
|
#[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord, |
|
|
Serialize, Deserialize)] |
|
|
|
|
|
|
|
|
Serialize)] |
|
|
#[allow(non_camel_case_types)] |
|
|
#[allow(non_camel_case_types)] |
|
|
#[repr(u8)] |
|
|
#[repr(u8)] |
|
|
pub enum Exchange { |
|
|
pub enum Exchange { |
|
@@ -436,11 +723,84 @@ macro_rules! make_exchange { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn to_str_uppercase(&self) -> &'static str { |
|
|
|
|
|
match *self { |
|
|
|
|
|
$( |
|
|
|
|
|
e!($ticker) => { stringify!($upper) } |
|
|
|
|
|
),* |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
pub fn all() -> Vec<Self> { |
|
|
pub fn all() -> Vec<Self> { |
|
|
vec![ |
|
|
vec![ |
|
|
$( e!($ticker) ),* |
|
|
$( e!($ticker) ),* |
|
|
] |
|
|
] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn variant_str_slice() -> &'static [&'static str] { |
|
|
|
|
|
&[ |
|
|
|
|
|
$( |
|
|
|
|
|
stringify!($ticker) |
|
|
|
|
|
),* |
|
|
|
|
|
] |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[allow(non_upper_case_globals)] |
|
|
|
|
|
pub fn from_bytes<'a>(bytes: &'a [u8]) -> Result<Self, UnlistedExchange<'a>> { |
|
|
|
|
|
// this declares for each currency the equivalent of |
|
|
|
|
|
// const BTC : &[u8] = "btc".as_bytes(); |
|
|
|
|
|
$( |
|
|
|
|
|
const $upper: &[u8] = stringify!($ticker).as_bytes(); |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
// this declares for each currency the equivalent of |
|
|
|
|
|
// const btc : &[u8] = "BTC".as_bytes(); |
|
|
|
|
|
$( |
|
|
|
|
|
const $ticker: &[u8] = stringify!($upper).as_bytes(); |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
match bytes { |
|
|
|
|
|
// first try lowercase (identified by uppercase consts) |
|
|
|
|
|
$( |
|
|
|
|
|
$upper => { return Ok(Exchange::$ticker) } |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
// then try uppercase (identified by lowercase consts) |
|
|
|
|
|
$( |
|
|
|
|
|
$ticker => { return Ok(Exchange::$ticker) } |
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
other => Err(UnlistedExchange(std::str::from_utf8(other).unwrap_or("<utf8 error>"))) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Optimized for expecting lowercase. Does not attempt to search beyond |
|
|
|
|
|
/// every currency symbol in lowercase. |
|
|
|
|
|
/// |
|
|
|
|
|
pub fn from_str_lowercase<'a>(s: &'a str) -> Result<Self, UnlistedExchange<'a>> { |
|
|
|
|
|
match s { |
|
|
|
|
|
$( |
|
|
|
|
|
stringify!($ticker) => { Ok(Exchange::$ticker) } |
|
|
|
|
|
|
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
other => Err(UnlistedExchange(other)) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Optimized for expecting uppercase. Does not attempt to search beyond |
|
|
|
|
|
/// every currency symbol in uppercase. |
|
|
|
|
|
/// |
|
|
|
|
|
pub fn from_str_uppercase<'a>(s: &'a str) -> Result<Self, UnlistedExchange<'a>> { |
|
|
|
|
|
match s { |
|
|
|
|
|
$( |
|
|
|
|
|
stringify!($upper) => { Ok(Exchange::$ticker) } |
|
|
|
|
|
|
|
|
|
|
|
)* |
|
|
|
|
|
|
|
|
|
|
|
other => Err(UnlistedExchange(other)) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
impl TryFrom<u8> for Exchange { |
|
|
impl TryFrom<u8> for Exchange { |
|
@@ -554,20 +914,65 @@ macro_rules! make_exchange { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn it_verifies_exch_lower_and_upper_consistency() { |
|
|
|
|
|
for x in Exchange::all() { |
|
|
|
|
|
assert_eq!(x.as_str().to_uppercase(), x.to_str_uppercase().to_string()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn checks_from_bytes_for_lower_and_upper_tickers_exch() { |
|
|
|
|
|
$( |
|
|
|
|
|
assert_eq!( |
|
|
|
|
|
Exchange::from_bytes(Exchange::$ticker.as_str().as_bytes()).unwrap(), |
|
|
|
|
|
Exchange::$ticker |
|
|
|
|
|
); |
|
|
|
|
|
assert_eq!( |
|
|
|
|
|
Exchange::from_bytes(Exchange::$ticker.to_str_uppercase().as_bytes()).unwrap(), |
|
|
|
|
|
Exchange::$ticker |
|
|
|
|
|
); |
|
|
|
|
|
)* |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn check_serde_json_deser_exch() { |
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)] |
|
|
|
|
|
struct A { |
|
|
|
|
|
pub exch: Exchange, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$( |
|
|
|
|
|
{ |
|
|
|
|
|
let a = A { exch: Exchange::$ticker }; |
|
|
|
|
|
let to_json = serde_json::to_string(&a).unwrap(); |
|
|
|
|
|
let manual_json = format!("{{\"exch\":\"{}\"}}", stringify!($ticker)); |
|
|
|
|
|
let manual_json_uppercase = format!("{{\"exch\":\"{}\"}}", stringify!($upper)); |
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&to_json).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&manual_json).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap()); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
)* |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
make_exchange!( |
|
|
make_exchange!( |
|
|
| plnx, "Poloniex", 1, |
|
|
|
|
|
| krkn, "Kraken", 2, |
|
|
|
|
|
| gdax, "GDAX", 3, |
|
|
|
|
|
| exmo, "Exmo", 4, |
|
|
|
|
|
| bits, "Bitstamp", 5, |
|
|
|
|
|
| bmex, "Bitmex", 6, |
|
|
|
|
|
| btfx, "Bitfinex", 7, |
|
|
|
|
|
| bnce, "Binance", 8, |
|
|
|
|
|
| okex, "OKEx", 9, |
|
|
|
|
|
| drbt, "Deribit", 10 |
|
|
|
|
|
|
|
|
| plnx, "Poloniex", 1, PLNX, |
|
|
|
|
|
| krkn, "Kraken", 2, KRKN, |
|
|
|
|
|
| gdax, "GDAX", 3, GDAX, |
|
|
|
|
|
| exmo, "Exmo", 4, EXMO, |
|
|
|
|
|
| bits, "Bitstamp", 5, BITS, |
|
|
|
|
|
| bmex, "Bitmex", 6, BMEX, |
|
|
|
|
|
| btfx, "Bitfinex", 7, BTFX, |
|
|
|
|
|
| bnce, "Binance", 8, BNCE, |
|
|
|
|
|
| okex, "OKEx", 9, OKEX, |
|
|
|
|
|
| drbt, "Deribit", 10, DRBT |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
#[test] |
|
|
#[test] |
|
@@ -776,6 +1181,32 @@ macro_rules! ticker_to_u8 { |
|
|
assert_eq!(t.as_str().to_uppercase(), t.to_str_uppercase().to_string()); |
|
|
assert_eq!(t.as_str().to_uppercase(), t.to_str_uppercase().to_string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn check_serde_json_deser_ticker() { |
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)] |
|
|
|
|
|
struct A { |
|
|
|
|
|
pub ticker: Ticker, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$( |
|
|
|
|
|
{ |
|
|
|
|
|
let a = A { ticker: Ticker { base: Currency::$base, quote: Currency::$quote } }; |
|
|
|
|
|
let to_json = serde_json::to_string(&a).unwrap(); |
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&to_json).unwrap()); |
|
|
|
|
|
|
|
|
|
|
|
for sep in ["_", "-", "/"].iter() { |
|
|
|
|
|
let manual_json = format!("{{\"ticker\":\"{}{}{}\"}}", stringify!($base), sep, stringify!($quote)); |
|
|
|
|
|
let manual_json_uppercase = format!("{{\"ticker\":\"{}{}{}\"}}", stringify!($u_base), sep, stringify!($u_quote)); |
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&manual_json).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap()); |
|
|
|
|
|
assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
)* |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@@ -871,20 +1302,9 @@ impl From<(Currency, Currency)> for Ticker { |
|
|
|
|
|
|
|
|
impl Serialize for Ticker { |
|
|
impl Serialize for Ticker { |
|
|
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> |
|
|
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> |
|
|
where S: Serializer { |
|
|
|
|
|
|
|
|
|
|
|
let s: String = format!("{}_{}", self.base, self.quote); |
|
|
|
|
|
serializer.serialize_str(&s) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl <'de> Deserialize<'de> for Ticker { |
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> |
|
|
|
|
|
where D: Deserializer<'de> { |
|
|
|
|
|
|
|
|
|
|
|
let s = String::deserialize(deserializer)?; |
|
|
|
|
|
Ticker::from_str(s.as_str()) |
|
|
|
|
|
.map_err(|_| de::Error::custom(format!("invalid Ticker '{}'", s))) |
|
|
|
|
|
|
|
|
where S: Serializer |
|
|
|
|
|
{ |
|
|
|
|
|
serializer.serialize_str(&format!("{}_{}", self.base, self.quote)) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@@ -915,7 +1335,7 @@ impl ::std::error::Error for Error { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[derive(Debug)] |
|
|
#[derive(Debug)] |
|
|
pub struct UnlistedTicker<'a>(&'a str); |
|
|
|
|
|
|
|
|
pub struct UnlistedTicker<'a>(pub &'a str); |
|
|
|
|
|
|
|
|
impl<'a> Display for UnlistedTicker<'a> { |
|
|
impl<'a> Display for UnlistedTicker<'a> { |
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
|
@@ -932,6 +1352,15 @@ impl<'a> Display for UnlistedCurrency<'a> { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)] |
|
|
|
|
|
pub struct UnlistedExchange<'a>(&'a str); |
|
|
|
|
|
|
|
|
|
|
|
impl<'a> Display for UnlistedExchange<'a> { |
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
|
|
|
|
|
write!(f, "UnlistedExchange({})", self.0) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
#[allow(unused)] |
|
|
#[allow(unused)] |
|
|
#[cfg(test)] |
|
|
#[cfg(test)] |
|
|
mod tests { |
|
|
mod tests { |
|
|