You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1523 lines
46KB

  1. //! Crypto currency and exchange tickers and other market-related primatives
  2. //!
  3. use std::fmt::{self, Display};
  4. use std::str::FromStr;
  5. use std::cmp::{PartialEq, Eq};
  6. use std::convert::TryFrom;
  7. use serde::{Serialize, Deserialize};
  8. use serde::de::{self, Deserializer, Visitor};
  9. use serde::Serializer;
  10. //use decimal::d128;
  11. //use chrono::{DateTime, Utc, TimeZone};
  12. #[macro_export]
  13. macro_rules! c {
  14. ($x:tt) => {
  15. $crate::crypto::Currency::$x
  16. }
  17. }
  18. #[macro_export]
  19. macro_rules! e {
  20. ($x:tt) => {
  21. $crate::crypto::Exchange::$x
  22. }
  23. }
  24. #[macro_export]
  25. macro_rules! t {
  26. ($base:tt-$quote:tt) => {
  27. $crate::crypto::Ticker {
  28. base: c!($base),
  29. quote: c!($quote)
  30. }
  31. }
  32. }
  33. /// which side of the transaction (buying or selling)
  34. /// a price/trade is
  35. ///
  36. #[repr(u8)]
  37. #[derive(Debug, PartialEq, Clone, Copy, Eq, Serialize)]
  38. pub enum Side {
  39. Bid = 1,
  40. Ask = 2,
  41. }
  42. impl TryFrom<u8> for Side {
  43. type Error = Error;
  44. fn try_from(n: u8) -> Result<Self, Self::Error> {
  45. match n {
  46. 1 => Ok(Side::Bid),
  47. 2 => Ok(Side::Ask),
  48. other => Err(Error::IllegalFormat(Box::new(format!("Expected 1 or 2 (received {})", other))))
  49. }
  50. }
  51. }
  52. impl From<Side> for u8 {
  53. fn from(side: Side) -> Self {
  54. match side {
  55. Side::Bid => 1,
  56. Side::Ask => 2
  57. }
  58. }
  59. }
  60. impl Side {
  61. pub fn as_verb(&self) -> &'static str {
  62. match *self {
  63. Side::Bid => "buy",
  64. Side::Ask => "sell"
  65. }
  66. }
  67. pub fn as_past_tense(&self) -> &'static str {
  68. match *self {
  69. Side::Bid => "bought",
  70. Side::Ask => "sold"
  71. }
  72. }
  73. pub fn as_past_tense_title_case(&self) -> &'static str {
  74. match *self {
  75. Side::Bid => "Bought",
  76. Side::Ask => "Sold"
  77. }
  78. }
  79. pub fn to_str(&self) -> &'static str {
  80. match *self {
  81. Side::Bid => "bid",
  82. Side::Ask => "ask"
  83. }
  84. }
  85. pub fn is_bid(&self) -> bool {
  86. match self {
  87. &Side::Bid => true,
  88. _ => false
  89. }
  90. }
  91. pub fn is_ask(&self) -> bool { !self.is_bid() }
  92. }
  93. impl <'de> Deserialize<'de> for Side {
  94. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  95. where D: Deserializer<'de> {
  96. let side = String::deserialize(deserializer)?;
  97. match side.to_lowercase().as_ref() {
  98. "bid" | "buy" | "bids" => Ok(Side::Bid),
  99. "ask" | "sell" | "asks" => Ok(Side::Ask),
  100. other => Err(de::Error::custom(format!("could not parse Side from '{}'", other))),
  101. }
  102. }
  103. }
  104. impl Display for Side {
  105. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  106. write!(f, "{:?}", self)
  107. }
  108. }
  109. impl FromStr for Side {
  110. type Err = Error;
  111. fn from_str(s: &str) -> Result<Self, Error> {
  112. match s.to_lowercase().as_ref() {
  113. "bid" | "buy" | "bids" => Ok(Side::Bid),
  114. "ask" | "sell" | "asks" => Ok(Side::Ask),
  115. s => Err(Error::IllegalFormat(Box::new(s.to_string())))
  116. }
  117. }
  118. }
  119. macro_rules! make_currency {
  120. //(@as_ident $name:ident) => { $name };
  121. ($(|$ticker:ident, $name:expr, $code:expr, $upper:ident),*) => {
  122. #[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord, Serialize)]
  123. #[allow(non_camel_case_types)]
  124. #[repr(u8)]
  125. pub enum Currency {
  126. $($ticker = $code),*
  127. }
  128. impl Currency {
  129. pub fn name(&self) -> &'static str {
  130. match *self {
  131. $(
  132. c!($ticker) => { $name }
  133. ),*
  134. }
  135. }
  136. pub fn as_str(&self) -> &'static str {
  137. match *self {
  138. $(
  139. c!($ticker) => { stringify!($ticker) }
  140. ),*
  141. }
  142. }
  143. pub fn to_str_uppercase(&self) -> &'static str {
  144. match *self {
  145. $(
  146. c!($ticker) => { stringify!($upper) }
  147. ),*
  148. }
  149. }
  150. #[allow(non_upper_case_globals)]
  151. pub fn from_bytes<'a>(bytes: &'a [u8]) -> Result<Self, UnlistedCurrency<'a>> {
  152. // this declares for each currency the equivalent of
  153. // const BTC : &[u8] = "btc".as_bytes();
  154. $(
  155. const $upper: &[u8] = stringify!($ticker).as_bytes();
  156. )*
  157. // this declares for each currency the equivalent of
  158. // const btc : &[u8] = "BTC".as_bytes();
  159. $(
  160. const $ticker: &[u8] = stringify!($upper).as_bytes();
  161. )*
  162. match bytes {
  163. // first try lowercase (identified by uppercase consts)
  164. $(
  165. $upper => { return Ok(Currency::$ticker) }
  166. )*
  167. // then try uppercase (identified by lowercase consts)
  168. $(
  169. $ticker => { return Ok(Currency::$ticker) }
  170. )*
  171. other => Err(UnlistedCurrency(std::str::from_utf8(other).unwrap_or("<utf8 error>")))
  172. }
  173. }
  174. /// Optimized for expecting lowercase. Does not attempt to search beyond
  175. /// every currency symbol in lowercase.
  176. ///
  177. pub fn from_str_lowercase<'a>(s: &'a str) -> Result<Self, UnlistedCurrency<'a>> {
  178. match s {
  179. $(
  180. stringify!($ticker) => { Ok(c!($ticker)) }
  181. )*
  182. other => Err(UnlistedCurrency(other))
  183. }
  184. }
  185. /// Optimized for expecting uppercase. Does not attempt to search beyond
  186. /// every currency symbol in uppercase.
  187. ///
  188. pub fn from_str_uppercase<'a>(s: &'a str) -> Result<Self, UnlistedCurrency<'a>> {
  189. match s {
  190. $(
  191. stringify!($upper) => { Ok(c!($ticker)) }
  192. )*
  193. other => Err(UnlistedCurrency(other))
  194. }
  195. }
  196. pub fn all() -> Vec<Self> {
  197. vec![
  198. $( c!($ticker) ),*
  199. ]
  200. }
  201. pub fn variant_str_slice() -> &'static [&'static str] {
  202. &[
  203. $(
  204. stringify!($ticker)
  205. ),*
  206. ]
  207. }
  208. }
  209. impl TryFrom<u8> for Currency {
  210. type Error = Error;
  211. fn try_from(n: u8) -> Result<Self, Self::Error> {
  212. match n {
  213. $(
  214. $code => { Ok(c!($ticker)) }
  215. ),*
  216. other => Err(Error::IllegalFormat(Box::new(format!("Illegal Currency value: {}", other))))
  217. }
  218. }
  219. }
  220. impl From<Currency> for u8 {
  221. fn from(c: Currency) -> Self {
  222. match c {
  223. $(
  224. c!($ticker) => { $code }
  225. ),*
  226. }
  227. }
  228. }
  229. impl FromStr for Currency {
  230. type Err = Error;
  231. fn from_str(s: &str) -> Result<Self, Self::Err> {
  232. let t = match s.to_lowercase().trim() {
  233. $(
  234. stringify!($ticker) => { c!($ticker) }
  235. ),*
  236. "xbt" => c!(btc),
  237. _ => {
  238. return Err(Error::IllegalCurrency);
  239. }
  240. };
  241. Ok(t)
  242. }
  243. }
  244. #[test]
  245. fn it_verifies_currency_lower_and_upper_consistency() {
  246. let currencies = vec![ $(c!($ticker)),* ];
  247. for c in &currencies {
  248. assert_eq!(c.as_str().to_uppercase(), c.to_str_uppercase().to_string());
  249. }
  250. }
  251. #[test]
  252. fn checks_from_bytes_for_lower_and_upper_tickers() {
  253. $(
  254. assert_eq!(
  255. Currency::from_bytes(Currency::$ticker.as_str().as_bytes()).unwrap(),
  256. Currency::$ticker
  257. );
  258. assert_eq!(
  259. Currency::from_bytes(Currency::$ticker.to_str_uppercase().as_bytes()).unwrap(),
  260. Currency::$ticker
  261. );
  262. )*
  263. }
  264. #[test]
  265. fn check_serde_json_deser() {
  266. #[derive(Serialize, Deserialize, PartialEq, Debug)]
  267. struct A {
  268. pub denom: Currency,
  269. }
  270. $(
  271. {
  272. let a = A { denom: Currency::$ticker };
  273. let to_json = serde_json::to_string(&a).unwrap();
  274. let manual_json = format!("{{\"denom\":\"{}\"}}", stringify!($ticker));
  275. let manual_json_uppercase = format!("{{\"denom\":\"{}\"}}", stringify!($upper));
  276. assert_eq!(a, serde_json::from_str(&to_json).unwrap());
  277. assert_eq!(a, serde_json::from_str(&manual_json).unwrap());
  278. assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap());
  279. assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap());
  280. assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap());
  281. }
  282. )*
  283. }
  284. }
  285. }
  286. make_currency!(
  287. | btc, "Bitcoin", 1, BTC,
  288. | eth, "Ethereum", 2, ETH,
  289. | xmr, "Monero", 3, XMR,
  290. | usdt, "Tether", 4, USDT,
  291. | ltc, "Litecoin", 5, LTC,
  292. | dash, "Dash", 6, DASH,
  293. | nvc, "Novacoin", 7, NVC,
  294. | ppc, "Peercoin", 8, PPC,
  295. | zec, "Zcash", 9, ZEC,
  296. | xrp, "Ripple", 10, XRP,
  297. | gnt, "Golem", 11, GNT,
  298. | steem, "Steem", 12, STEEM,
  299. | rep, "Augur", 13, REP,
  300. | gno, "Gnosis", 14, GNO,
  301. | etc, "Ethereum Classic", 15, ETC,
  302. | icn, "Iconomi", 16, ICN,
  303. | xlm, "Stellar", 17, XLM,
  304. | mln, "Melon", 18, MLN,
  305. | bcn, "Bytecoin", 19, BCN,
  306. | bch, "Bitcoin Cash", 20, BCH,
  307. | doge, "Dogecoin", 21, DOGE,
  308. | eos, "Eos", 22, EOS,
  309. | nxt, "Nxt", 23, NXT,
  310. | sc, "Siacoin", 24, SC,
  311. | zrx, "0x", 25, ZRX,
  312. | bat, "Basic Attention Token", 26, BAT,
  313. | ada, "Cardano", 27, ADA,
  314. | usdc, "USD Coin", 28, USDC,
  315. | dai, "Dai", 29, DAI,
  316. | mkr, "Maker", 30, MKR,
  317. | loom, "Loom Network", 31, LOOM,
  318. | cvc, "Civic", 32, CVC,
  319. | mana, "Decentraland", 33, MANA,
  320. | dnt, "district0x", 34, DNT,
  321. | zil, "Zilliqa", 35, ZIL,
  322. | link, "Chainlink", 36, LINK,
  323. | algo, "Algorand", 37, ALGO,
  324. | xtz, "Tezos", 38, XTZ,
  325. | oxt, "Orchid", 39, OXT,
  326. | atom, "Cosmos", 40, ATOM,
  327. // fiat: u8 code starts at 100 (can be used to filter/sort)
  328. | usd, "U.S. Dollar", 100, USD,
  329. | eur, "Euro", 101, EUR,
  330. | rur, "Ruble", 102, RUR,
  331. | jpy, "Yen", 103, JPY,
  332. | gbp, "Pound", 104, GBP,
  333. | chf, "Franc", 105, CHF,
  334. | cad, "Canadian Dollar", 106, CAD,
  335. | aud, "Australian Dollar", 107, AUD,
  336. | zar, "Rand", 108, ZAR,
  337. | mxn, "Peso", 109, MXN
  338. );
  339. impl Currency {
  340. /// Convert fiat peg to tether equivalent.
  341. ///
  342. /// # Examples
  343. ///
  344. /// ```
  345. /// #[macro_use]
  346. /// extern crate markets;
  347. /// fn main() {
  348. /// assert_eq!(c!(usd).to_tether(), c!(usdt));
  349. /// assert_eq!(c!(btc).to_tether(), c!(btc));
  350. /// }
  351. /// ```
  352. ///
  353. pub fn to_tether(&self) -> Self {
  354. match *self {
  355. c!(usd) => c!(usdt),
  356. other => other
  357. }
  358. }
  359. /// Converts stablecoins into fiat peg.
  360. ///
  361. /// # Examples
  362. ///
  363. /// ```
  364. /// #[macro_use]
  365. /// extern crate markets;
  366. /// fn main() {
  367. /// assert_eq!(c!(usdt).from_tether(), c!(usd));
  368. /// assert_eq!(c!(btc).from_tether(), c!(btc));
  369. /// }
  370. /// ```
  371. ///
  372. pub fn from_tether(&self) -> Self {
  373. match *self {
  374. c!(usdt) => c!(usd),
  375. other => other
  376. }
  377. }
  378. }
  379. /// # Panics
  380. ///
  381. /// The `TryFrom` implementation in this macro will panic it is passed
  382. /// a value greater than the maximum value a `u8` can store.
  383. ///
  384. macro_rules! use_u8_impl_for_int {
  385. ($t:ty, $int:ty) => {
  386. impl From<$t> for $int {
  387. fn from(t: $t) -> Self {
  388. u8::from(t) as $int
  389. }
  390. }
  391. impl TryFrom<$int> for $t {
  392. type Error = Error;
  393. fn try_from(n: $int) -> Result<Self, Self::Error> {
  394. TryFrom::try_from(n as u8)
  395. }
  396. }
  397. }
  398. }
  399. use_u8_impl_for_int!(Currency, i16);
  400. use_u8_impl_for_int!(Exchange, i16);
  401. use_u8_impl_for_int!(Ticker, i16);
  402. use_u8_impl_for_int!(Side, i16);
  403. #[test]
  404. fn check_generated_currency_fns() {
  405. assert_eq!(c!(usd), Currency::usd);
  406. assert_eq!(c!(usd).name(), "U.S. Dollar");
  407. assert_eq!(Currency::try_from(1_u8).unwrap(), c!(btc));
  408. assert_eq!(u8::from(c!(usd)), 100_u8);
  409. assert_eq!(i16::from(c!(usd)), 100_i16);
  410. }
  411. struct CurrencyVisitor;
  412. struct ExchangeVisitor;
  413. struct TickerVisitor;
  414. impl<'de> Visitor<'de> for CurrencyVisitor {
  415. type Value = Currency;
  416. fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
  417. formatter.write_str("a currency symbol or ticker, like those used by exchanges (e.g. btc, usd)")
  418. }
  419. fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
  420. E: de::Error
  421. {
  422. Currency::from_str_lowercase(v)
  423. .or_else(|_| Currency::from_str_uppercase(v))
  424. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))
  425. }
  426. fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where
  427. E: de::Error
  428. {
  429. Currency::from_str_lowercase(v)
  430. .or_else(|_| Currency::from_str_uppercase(v))
  431. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))
  432. }
  433. fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where
  434. E: de::Error
  435. {
  436. Currency::from_bytes(v)
  437. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))
  438. }
  439. fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> where
  440. E: de::Error
  441. {
  442. Currency::from_bytes(v)
  443. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))
  444. }
  445. }
  446. impl<'de> Visitor<'de> for ExchangeVisitor {
  447. type Value = Exchange;
  448. fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
  449. formatter.write_str("a four-character exchange symbol (e.g. gdax, bmex)")
  450. }
  451. fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
  452. E: de::Error
  453. {
  454. Exchange::from_str_lowercase(v)
  455. .or_else(|_| Exchange::from_str_uppercase(v))
  456. .map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice()))
  457. }
  458. fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where
  459. E: de::Error
  460. {
  461. Exchange::from_str_lowercase(v)
  462. .or_else(|_| Exchange::from_str_uppercase(v))
  463. .map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice()))
  464. }
  465. fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where
  466. E: de::Error
  467. {
  468. Exchange::from_bytes(v)
  469. .map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice()))
  470. }
  471. fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> where
  472. E: de::Error
  473. {
  474. Exchange::from_bytes(v)
  475. .map_err(|UnlistedExchange(unknown)| E::unknown_variant(unknown, Exchange::variant_str_slice()))
  476. }
  477. }
  478. impl<'de> Visitor<'de> for TickerVisitor {
  479. type Value = Ticker;
  480. fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
  481. formatter.write_str("a currency rate symbol or ticker, like those used by exchanges (e.g. btc_usd)")
  482. }
  483. fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
  484. E: de::Error
  485. {
  486. let mut parts = v.split(|c| c == '_' || c == '-' || c == '/');
  487. match parts.next() {
  488. Some(base_str) => {
  489. match parts.next() {
  490. Some(quote_str) => {
  491. let base = Currency::from_str_lowercase(base_str)
  492. .or_else(|_| Currency::from_str_uppercase(base_str))
  493. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  494. let quote = Currency::from_str_lowercase(quote_str)
  495. .or_else(|_| Currency::from_str_uppercase(quote_str))
  496. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  497. Ok(Ticker { base, quote })
  498. }
  499. None => Err(E::missing_field("quote"))
  500. }
  501. }
  502. None => Err(E::missing_field("base"))
  503. }
  504. }
  505. fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where
  506. E: de::Error
  507. {
  508. let mut parts = v.split(|c| c == '_' || c == '-' || c == '/');
  509. match parts.next() {
  510. Some(base_str) => {
  511. match parts.next() {
  512. Some(quote_str) => {
  513. let base = Currency::from_str_lowercase(base_str)
  514. .or_else(|_| Currency::from_str_uppercase(base_str))
  515. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  516. let quote = Currency::from_str_lowercase(quote_str)
  517. .or_else(|_| Currency::from_str_uppercase(quote_str))
  518. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  519. Ok(Ticker { base, quote })
  520. }
  521. None => Err(E::missing_field("quote"))
  522. }
  523. }
  524. None => Err(E::missing_field("base"))
  525. }
  526. }
  527. fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where
  528. E: de::Error
  529. {
  530. let mut parts = v.split(|&c| c == b'_' || c == b'-' || c == b'/');
  531. match parts.next() {
  532. Some(base_str) => {
  533. match parts.next() {
  534. Some(quote_str) => {
  535. let base = Currency::from_bytes(base_str)
  536. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  537. let quote = Currency::from_bytes(quote_str)
  538. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  539. Ok(Ticker { base, quote })
  540. }
  541. None => Err(E::missing_field("quote"))
  542. }
  543. }
  544. None => Err(E::missing_field("base"))
  545. }
  546. }
  547. fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> where
  548. E: de::Error
  549. {
  550. let mut parts = v.split(|&c| c == b'_' || c == b'-' || c == b'/');
  551. match parts.next() {
  552. Some(base_str) => {
  553. match parts.next() {
  554. Some(quote_str) => {
  555. let base = Currency::from_bytes(base_str)
  556. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  557. let quote = Currency::from_bytes(quote_str)
  558. .map_err(|UnlistedCurrency(unknown)| E::unknown_variant(unknown, Currency::variant_str_slice()))?;
  559. Ok(Ticker { base, quote })
  560. }
  561. None => Err(E::missing_field("quote"))
  562. }
  563. }
  564. None => Err(E::missing_field("base"))
  565. }
  566. }
  567. }
  568. impl <'de> Deserialize<'de> for Currency {
  569. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  570. where D: Deserializer<'de>
  571. {
  572. deserializer.deserialize_bytes(CurrencyVisitor)
  573. }
  574. }
  575. impl <'de> Deserialize<'de> for Exchange {
  576. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  577. where D: Deserializer<'de>
  578. {
  579. deserializer.deserialize_bytes(ExchangeVisitor)
  580. }
  581. }
  582. impl <'de> Deserialize<'de> for Ticker {
  583. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  584. where D: Deserializer<'de>
  585. {
  586. deserializer.deserialize_bytes(TickerVisitor)
  587. }
  588. }
  589. impl Display for Currency {
  590. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  591. write!(f, "{:?}", self)
  592. }
  593. }
  594. macro_rules! make_exchange {
  595. ($(|$ticker:ident, $name:expr, $code:expr, $upper:ident),*) => {
  596. #[derive(Debug, PartialEq, Clone, Hash, Eq, Copy, PartialOrd, Ord,
  597. Serialize)]
  598. #[allow(non_camel_case_types)]
  599. #[repr(u8)]
  600. pub enum Exchange {
  601. $($ticker = $code),*
  602. }
  603. impl Exchange {
  604. pub fn name(&self) -> &'static str {
  605. match *self {
  606. $(
  607. e!($ticker) => { $name }
  608. ),*
  609. }
  610. }
  611. pub fn as_str(&self) -> &'static str {
  612. match *self {
  613. $(
  614. e!($ticker) => { stringify!($ticker) }
  615. ),*
  616. }
  617. }
  618. pub fn to_str_uppercase(&self) -> &'static str {
  619. match *self {
  620. $(
  621. e!($ticker) => { stringify!($upper) }
  622. ),*
  623. }
  624. }
  625. pub fn all() -> Vec<Self> {
  626. vec![
  627. $( e!($ticker) ),*
  628. ]
  629. }
  630. pub fn variant_str_slice() -> &'static [&'static str] {
  631. &[
  632. $(
  633. stringify!($ticker)
  634. ),*
  635. ]
  636. }
  637. #[allow(non_upper_case_globals)]
  638. pub fn from_bytes<'a>(bytes: &'a [u8]) -> Result<Self, UnlistedExchange<'a>> {
  639. // this declares for each currency the equivalent of
  640. // const BTC : &[u8] = "btc".as_bytes();
  641. $(
  642. const $upper: &[u8] = stringify!($ticker).as_bytes();
  643. )*
  644. // this declares for each currency the equivalent of
  645. // const btc : &[u8] = "BTC".as_bytes();
  646. $(
  647. const $ticker: &[u8] = stringify!($upper).as_bytes();
  648. )*
  649. match bytes {
  650. // first try lowercase (identified by uppercase consts)
  651. $(
  652. $upper => { return Ok(Exchange::$ticker) }
  653. )*
  654. // then try uppercase (identified by lowercase consts)
  655. $(
  656. $ticker => { return Ok(Exchange::$ticker) }
  657. )*
  658. other => Err(UnlistedExchange(std::str::from_utf8(other).unwrap_or("<utf8 error>")))
  659. }
  660. }
  661. /// Optimized for expecting lowercase. Does not attempt to search beyond
  662. /// every currency symbol in lowercase.
  663. ///
  664. pub fn from_str_lowercase<'a>(s: &'a str) -> Result<Self, UnlistedExchange<'a>> {
  665. match s {
  666. $(
  667. stringify!($ticker) => { Ok(Exchange::$ticker) }
  668. )*
  669. other => Err(UnlistedExchange(other))
  670. }
  671. }
  672. /// Optimized for expecting uppercase. Does not attempt to search beyond
  673. /// every currency symbol in uppercase.
  674. ///
  675. pub fn from_str_uppercase<'a>(s: &'a str) -> Result<Self, UnlistedExchange<'a>> {
  676. match s {
  677. $(
  678. stringify!($upper) => { Ok(Exchange::$ticker) }
  679. )*
  680. other => Err(UnlistedExchange(other))
  681. }
  682. }
  683. }
  684. impl TryFrom<u8> for Exchange {
  685. type Error = Error;
  686. fn try_from(n: u8) -> Result<Self, Self::Error> {
  687. match n {
  688. $(
  689. $code => { Ok(e!($ticker)) }
  690. ),*
  691. _ => Err(Error::IllegalExchangeCode(n))
  692. }
  693. }
  694. }
  695. impl From<Exchange> for u8 {
  696. fn from(c: Exchange) -> Self {
  697. match c {
  698. $(
  699. e!($ticker) => { $code }
  700. ),*
  701. }
  702. }
  703. }
  704. impl FromStr for Exchange {
  705. type Err = Error;
  706. fn from_str(s: &str) -> Result<Self, Self::Err> {
  707. let t = match s.to_lowercase().trim() {
  708. $(
  709. stringify!($ticker) => { e!($ticker) }
  710. ),*
  711. _ => {
  712. return Err(Error::IllegalExchange);
  713. }
  714. };
  715. Ok(t)
  716. }
  717. }
  718. /// Holds an object of the same type for each exchange.
  719. ///
  720. pub struct ByExchange<T> {
  721. $(
  722. pub $ticker: T
  723. ),*
  724. }
  725. impl<T> ByExchange<T> {
  726. pub fn new(
  727. $(
  728. $ticker: T
  729. ),*
  730. ) -> Self {
  731. Self {
  732. $(
  733. $ticker
  734. ),*
  735. }
  736. }
  737. pub fn of(&self, exchange: &Exchange) -> &T {
  738. match exchange {
  739. $(
  740. &e!($ticker) => &self.$ticker
  741. ),*
  742. }
  743. }
  744. pub fn of_mut(&mut self, exchange: &Exchange) -> &mut T {
  745. match exchange {
  746. $(
  747. &e!($ticker) => &mut self.$ticker
  748. ),*
  749. }
  750. }
  751. // to match map apis:
  752. pub fn get(&self, exchange: &Exchange) -> Option<&T> {
  753. Some(self.of(exchange))
  754. }
  755. pub fn get_mut(&mut self, exchange: &Exchange) -> Option<&mut T> {
  756. Some(self.of_mut(exchange))
  757. }
  758. }
  759. impl<T> Default for ByExchange<T>
  760. where T: Default
  761. {
  762. fn default() -> Self {
  763. ByExchange {
  764. $(
  765. $ticker: Default::default()
  766. ),*
  767. }
  768. }
  769. }
  770. impl<T> Clone for ByExchange<T>
  771. where T: Clone
  772. {
  773. fn clone(&self) -> Self {
  774. ByExchange {
  775. $(
  776. $ticker: self.$ticker.clone()
  777. ),*
  778. }
  779. }
  780. }
  781. #[test]
  782. fn it_verifies_exch_lower_and_upper_consistency() {
  783. for x in Exchange::all() {
  784. assert_eq!(x.as_str().to_uppercase(), x.to_str_uppercase().to_string());
  785. }
  786. }
  787. #[test]
  788. fn checks_from_bytes_for_lower_and_upper_tickers_exch() {
  789. $(
  790. assert_eq!(
  791. Exchange::from_bytes(Exchange::$ticker.as_str().as_bytes()).unwrap(),
  792. Exchange::$ticker
  793. );
  794. assert_eq!(
  795. Exchange::from_bytes(Exchange::$ticker.to_str_uppercase().as_bytes()).unwrap(),
  796. Exchange::$ticker
  797. );
  798. )*
  799. }
  800. #[test]
  801. fn check_serde_json_deser_exch() {
  802. #[derive(Serialize, Deserialize, PartialEq, Debug)]
  803. struct A {
  804. pub exch: Exchange,
  805. }
  806. $(
  807. {
  808. let a = A { exch: Exchange::$ticker };
  809. let to_json = serde_json::to_string(&a).unwrap();
  810. let manual_json = format!("{{\"exch\":\"{}\"}}", stringify!($ticker));
  811. let manual_json_uppercase = format!("{{\"exch\":\"{}\"}}", stringify!($upper));
  812. assert_eq!(a, serde_json::from_str(&to_json).unwrap());
  813. assert_eq!(a, serde_json::from_str(&manual_json).unwrap());
  814. assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap());
  815. assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap());
  816. assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap());
  817. }
  818. )*
  819. }
  820. }
  821. }
  822. make_exchange!(
  823. | plnx, "Poloniex", 1, PLNX,
  824. | krkn, "Kraken", 2, KRKN,
  825. | gdax, "GDAX", 3, GDAX,
  826. | exmo, "Exmo", 4, EXMO,
  827. | bits, "Bitstamp", 5, BITS,
  828. | bmex, "Bitmex", 6, BMEX,
  829. | btfx, "Bitfinex", 7, BTFX,
  830. | bnce, "Binance", 8, BNCE,
  831. | okex, "OKEx", 9, OKEX,
  832. | drbt, "Deribit", 10, DRBT
  833. );
  834. #[test]
  835. fn check_generated_exchange_fns() {
  836. assert_eq!(e!(plnx), Exchange::plnx);
  837. assert_eq!(e!(plnx).name(), "Poloniex");
  838. assert_eq!(Exchange::try_from(2_u8).unwrap(), e!(krkn));
  839. assert_eq!(u8::from(e!(gdax)), 3_u8);
  840. }
  841. impl Display for Exchange {
  842. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  843. write!(f, "{}", self.as_str())
  844. }
  845. }
  846. #[derive(Debug, Clone, Hash, PartialEq, Eq, Copy, PartialOrd, Ord)]
  847. pub struct Ticker {
  848. pub base: Currency,
  849. pub quote: Currency,
  850. }
  851. impl Ticker {
  852. pub fn flip(&self) -> Self {
  853. Ticker { base: self.quote, quote: self.base }
  854. }
  855. pub fn to_s(&self) -> String {
  856. format!("{}", self)
  857. }
  858. }
  859. macro_rules! ticker_to_u8 {
  860. ($(|$base:tt-$quote:tt, $u_base:tt-$u_quote:tt, $n:expr),*) => {
  861. impl From<Ticker> for u8 {
  862. fn from(t: Ticker) -> Self {
  863. match t {
  864. $(
  865. t!($base-$quote) => { $n }
  866. ),*
  867. _ => 0
  868. }
  869. }
  870. }
  871. impl TryFrom<u8> for Ticker {
  872. type Error = Error;
  873. fn try_from(n: u8) -> Result<Self, Self::Error> {
  874. match n {
  875. $(
  876. $n => { Ok(t!($base-$quote)) }
  877. ),*
  878. other => Err(Error::IllegalValue(other))
  879. }
  880. }
  881. }
  882. impl Ticker {
  883. pub fn as_str(&self) -> &'static str {
  884. match *self {
  885. $(
  886. t!($base-$quote) => { concat!(stringify!($base), "_", stringify!($quote)) }
  887. ),*
  888. _ => "xxx_xxx"
  889. }
  890. }
  891. pub fn as_str_dash(&self) -> &'static str {
  892. match *self {
  893. $( t!($base-$quote) => { concat!(stringify!($base), "-", stringify!($quote)) } ),*
  894. _ => "xxx-xxx"
  895. }
  896. }
  897. pub fn to_str_uppercase(&self) -> &'static str {
  898. match *self {
  899. $(
  900. t!($base-$quote) => { concat!(stringify!($u_base), "_", stringify!($u_quote)) }
  901. ),*
  902. _ => "XXX_XXX"
  903. }
  904. }
  905. pub fn to_str_reversed(&self) -> &'static str {
  906. match *self {
  907. $(
  908. t!($base-$quote) => { concat!(stringify!($quote), "_", stringify!($base)) }
  909. ),*
  910. _ => "xxx_xxx"
  911. }
  912. }
  913. pub fn to_str_uppercase_reversed(&self) -> &'static str {
  914. match *self {
  915. $(
  916. t!($base-$quote) => { concat!(stringify!($u_quote), "_", stringify!($u_base)) }
  917. ),*
  918. _ => "XXX_XXX"
  919. }
  920. }
  921. /// t!(btc-usd) -> "BTC-USD"
  922. ///
  923. pub fn to_str_uppercase_dash_sep(&self) -> &'static str {
  924. match *self {
  925. $(
  926. t!($base-$quote) => { concat!(stringify!($u_base), "-", stringify!($u_quote)) }
  927. ),*
  928. _ => "XXX-XXX"
  929. }
  930. }
  931. pub fn as_gdax_str(&self) -> &'static str { self.to_str_uppercase_dash_sep() }
  932. /// Note - this performs the flip and fails for tickers not in the list.
  933. ///
  934. pub fn from_str_uppercase_reverse<'a>(s: &'a str) -> Result<Ticker, UnlistedTicker<'a>> {
  935. match s {
  936. $(
  937. concat!(stringify!($u_quote), "_", stringify!($u_base)) => { Ok(t!($base-$quote)) }
  938. )*
  939. other => Err(UnlistedTicker(other))
  940. }
  941. }
  942. pub fn from_str_uppercase_dash_sep(s: &str) -> Result<Ticker, Error> {
  943. match s {
  944. $(
  945. concat!(stringify!($u_base), "-", stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  946. )*
  947. other => ticker_parse_last_resort(other)
  948. }
  949. }
  950. pub fn from_str_bitfinex(s: &str) -> Result<Ticker, Error> {
  951. match s {
  952. $(
  953. concat!(stringify!($u_base), stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  954. )*
  955. other => ticker_parse_last_resort(other)
  956. }
  957. }
  958. pub fn from_str_bitmex(s: &str) -> Result<Ticker, Error> {
  959. match s {
  960. "XBTUSD" => Ok(t!(btc-usd)),
  961. "ETHUSD" => Ok(t!(eth-usd)),
  962. $(
  963. concat!(stringify!($u_base), stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  964. )*
  965. other => ticker_parse_last_resort(other)
  966. }
  967. }
  968. }
  969. fn ticker_parse_last_resort(other: &str) -> Result<Ticker, Error> {
  970. match other.find('_').or_else(|| other.find('-')).or_else(|| other.find('/')) {
  971. Some(sep) if sep < other.len() - 1 => {
  972. let base = Currency::from_str(&other[..sep])?;
  973. let quote = Currency::from_str(&other[sep + 1..])?;
  974. Ok(Ticker { base, quote })
  975. }
  976. _ => Err(Error::IllegalFormat(Box::new(format!("failed to parse Ticker '{}'", other))))
  977. }
  978. }
  979. impl FromStr for Ticker {
  980. type Err = Error;
  981. fn from_str(s: &str) -> Result<Self, Error> {
  982. match s {
  983. $(
  984. concat!(stringify!($base), "_", stringify!($quote))
  985. //| concat!(stringify!($base), "-", stringify!($quote))
  986. //| concat!(stringify!($base), "/", stringify!($quote))
  987. //| concat!(stringify!($u_base), "_", stringify!($u_quote))
  988. //| concat!(stringify!($u_base), "-", stringify!($u_quote))
  989. //| concat!(stringify!($u_base), "-", stringify!($u_quote))
  990. => { Ok(t!($base-$quote)) }
  991. )*
  992. other => ticker_parse_last_resort(other)
  993. }
  994. }
  995. }
  996. #[test]
  997. fn it_verifies_ticker_lower_and_upper_consistency() {
  998. let tickers = vec![ $(t!($base-$quote)),*];
  999. for t in &tickers {
  1000. assert_eq!(t.as_str().to_uppercase(), t.to_str_uppercase().to_string());
  1001. }
  1002. }
  1003. #[test]
  1004. fn check_serde_json_deser_ticker() {
  1005. #[derive(Serialize, Deserialize, PartialEq, Debug)]
  1006. struct A {
  1007. pub ticker: Ticker,
  1008. }
  1009. $(
  1010. {
  1011. let a = A { ticker: Ticker { base: Currency::$base, quote: Currency::$quote } };
  1012. let to_json = serde_json::to_string(&a).unwrap();
  1013. assert_eq!(a, serde_json::from_str(&to_json).unwrap());
  1014. for sep in ["_", "-", "/"].iter() {
  1015. let manual_json = format!("{{\"ticker\":\"{}{}{}\"}}", stringify!($base), sep, stringify!($quote));
  1016. let manual_json_uppercase = format!("{{\"ticker\":\"{}{}{}\"}}", stringify!($u_base), sep, stringify!($u_quote));
  1017. assert_eq!(a, serde_json::from_str(&manual_json).unwrap());
  1018. assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap());
  1019. assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap());
  1020. assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap());
  1021. }
  1022. }
  1023. )*
  1024. }
  1025. }
  1026. }
  1027. ticker_to_u8!(
  1028. |btc-usd, BTC-USD, 1,
  1029. |xmr-usd, XMR-USD, 2,
  1030. |eth-usd, ETH-USD, 3,
  1031. |ltc-usd, LTC-USD, 4,
  1032. |etc-usd, ETC-USD, 5,
  1033. |xrp-usd, XRP-USD, 6,
  1034. |bch-usd, BCH-USD, 7,
  1035. |zec-usd, ZEC-USD, 8,
  1036. |dash-usd, DASH-USD, 9,
  1037. |rep-usd, REP-USD, 10,
  1038. |btc-usdt, BTC-USDT, 11,
  1039. |xmr-usdt, XMR-USDT, 12,
  1040. |eth-usdt, ETH-USDT, 13,
  1041. |ltc-usdt, LTC-USDT, 14,
  1042. |etc-usdt, ETC-USDT, 15,
  1043. |xrp-usdt, XRP-USDT, 16,
  1044. |bch-usdt, BCH-USDT, 17,
  1045. |zec-usdt, ZEC-USDT, 18,
  1046. |dash-usdt,DASH-USDT,19,
  1047. |rep-usdt, REP-USDT, 20,
  1048. |xmr-btc, XMR-BTC, 21,
  1049. |eth-btc, ETH-BTC, 22,
  1050. |ltc-btc, LTC-BTC, 23,
  1051. |etc-btc, ETC-BTC, 24,
  1052. |xrp-btc, XRP-BTC, 25,
  1053. |bch-btc, BCH-BTC, 26,
  1054. |zec-btc, ZEC-BTC, 27,
  1055. |dash-btc, DASH-BTC, 28,
  1056. |rep-btc, REP-BTC, 29,
  1057. |zec-eth, ZEC-ETH, 30,
  1058. |etc-eth, ETC-ETH, 31,
  1059. |bch-eth, BCH-ETH, 32,
  1060. |rep-eth, REP-ETH, 33,
  1061. |btc-eur, BTC-EUR, 34,
  1062. |btc-jpy, BTC-JPY, 35,
  1063. |btc-gbp, BTC-GBP, 36,
  1064. |btc-cad, BTC-CAD, 37,
  1065. |eth-eur, ETH-EUR, 38,
  1066. |eth-jpy, ETH-JPY, 39,
  1067. |eth-gbp, ETH-GBP, 40,
  1068. |eth-cad, ETH-CAD, 41,
  1069. |ltc-eur, LTC-EUR, 42,
  1070. |ltc-jpy, LTC-JPY, 43,
  1071. |ltc-gbp, LTC-GBP, 44,
  1072. |ltc-cad, LTC-CAD, 45,
  1073. |xmr-eur, XMR-EUR, 46,
  1074. |bch-eur, BCH-EUR, 47,
  1075. |rep-eur, REP-EUR, 48,
  1076. |etc-eur, ETC-EUR, 49,
  1077. |usdt-usd, USDT-USD, 50
  1078. // Note: a trailing comma will throw off the whole thing.
  1079. );
  1080. #[test]
  1081. fn check_generated_ticker_fns() {
  1082. assert_eq!(t!(btc-usd), Ticker { base: Currency::btc, quote: Currency::usd });
  1083. assert_eq!(t!(btc-usd).as_str(), "btc_usd");
  1084. assert_eq!(t!(eth-jpy).as_str(), "eth_jpy");
  1085. assert_eq!(t!(usd-btc).as_str(), "xxx_xxx");
  1086. assert_eq!(u8::from(t!(btc-usd)), 1_u8);
  1087. assert_eq!(u8::from(t!(btc-cad)), 37_u8);
  1088. assert_eq!(Ticker::try_from(1_u8).unwrap(), t!(btc-usd));
  1089. assert_eq!(Ticker::try_from(21_u8).unwrap(), t!(xmr-btc));
  1090. assert!(Ticker::try_from(121_u8).is_err());
  1091. }
  1092. impl Into<String> for Ticker {
  1093. fn into(self) -> String {
  1094. format!("{}_{}", self.base, self.quote)
  1095. }
  1096. }
  1097. impl From<(Currency, Currency)> for Ticker {
  1098. fn from(basequote: (Currency, Currency)) -> Self {
  1099. let (base, quote) = basequote;
  1100. Ticker { base, quote }
  1101. }
  1102. }
  1103. impl Serialize for Ticker {
  1104. fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
  1105. where S: Serializer
  1106. {
  1107. serializer.serialize_str(&format!("{}_{}", self.base, self.quote))
  1108. }
  1109. }
  1110. impl Display for Ticker {
  1111. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1112. write!(f, "{}_{}", self.base, self.quote)
  1113. }
  1114. }
  1115. #[derive(Debug, Clone)]
  1116. pub enum Error {
  1117. IllegalCurrency,
  1118. IllegalExchange,
  1119. IllegalValue(u8),
  1120. IllegalFormat(Box<String>),
  1121. IllegalExchangeCode(u8),
  1122. NaN,
  1123. }
  1124. impl Display for Error {
  1125. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1126. write!(f, "{:?}", self)
  1127. }
  1128. }
  1129. impl ::std::error::Error for Error {
  1130. fn description(&self) -> &str { "MoneyError" }
  1131. }
  1132. #[derive(Debug)]
  1133. pub struct UnlistedTicker<'a>(pub &'a str);
  1134. impl<'a> Display for UnlistedTicker<'a> {
  1135. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1136. write!(f, "UnlistedTicker({})", self.0)
  1137. }
  1138. }
  1139. #[derive(Debug)]
  1140. pub struct UnlistedCurrency<'a>(&'a str);
  1141. impl<'a> Display for UnlistedCurrency<'a> {
  1142. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1143. write!(f, "UnlistedCurrency({})", self.0)
  1144. }
  1145. }
  1146. #[derive(Debug)]
  1147. pub struct UnlistedExchange<'a>(&'a str);
  1148. impl<'a> Display for UnlistedExchange<'a> {
  1149. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1150. write!(f, "UnlistedExchange({})", self.0)
  1151. }
  1152. }
  1153. #[allow(unused)]
  1154. #[cfg(test)]
  1155. mod tests {
  1156. use super::*;
  1157. #[cfg(feature = "unstable")]
  1158. use test::{black_box, Bencher};
  1159. #[test]
  1160. fn ticker_from_str_advanced() {
  1161. assert_eq!(Ticker::from_str("btc_usd").unwrap(), t!(btc-usd));
  1162. assert_eq!(Ticker::from_str("btc-usd").unwrap(), t!(btc-usd));
  1163. assert_eq!(Ticker::from_str("btc/usd").unwrap(), t!(btc-usd));
  1164. assert_eq!(Ticker::from_str("BTC_USD").unwrap(), t!(btc-usd));
  1165. assert_eq!(Ticker::from_str("BTC-USD").unwrap(), t!(btc-usd));
  1166. assert_eq!(Ticker::from_str("BTC/USD").unwrap(), t!(btc-usd));
  1167. assert_eq!(Ticker::from_str("USD_BTC").unwrap(), t!(usd-btc));
  1168. assert!(Ticker::from_str("not_a_ticker_format").is_err());
  1169. }
  1170. #[test]
  1171. fn it_parses_bcn_ticker() {
  1172. let ticker = Ticker::from_str("bcn_btc").unwrap();
  1173. }
  1174. #[test]
  1175. fn it_checks_simple_macro_use() {
  1176. let btc_cur = c!(btc);
  1177. assert_eq!(btc_cur, Currency::btc);
  1178. let plnx_exch = e!(plnx);
  1179. assert_eq!(plnx_exch, Exchange::plnx);
  1180. let eth_usd_ticker = t!(eth-usd);
  1181. assert_eq!(eth_usd_ticker.base, c!(eth));
  1182. assert_eq!(eth_usd_ticker.quote, c!(usd));
  1183. }
  1184. #[cfg(feature = "unstable")]
  1185. #[bench]
  1186. fn ticker_to_str_obfuscated(b: &mut Bencher) {
  1187. let ticker = Ticker::from_str("btc_usd").unwrap();
  1188. b.iter(|| {
  1189. black_box(ticker.as_str())
  1190. });
  1191. }
  1192. #[cfg(feature = "unstable")]
  1193. #[bench]
  1194. fn ticker_from_str_bench(b: &mut Bencher) {
  1195. let t = black_box(String::from("btc_usd"));
  1196. b.iter(||{
  1197. Ticker::from_str(&t).unwrap()
  1198. });
  1199. }
  1200. #[cfg(feature = "unstable")]
  1201. #[bench]
  1202. fn ticker_from_str_bench_last(b: &mut Bencher) {
  1203. let t = black_box(String::from("ltc_cad"));
  1204. b.iter(||{
  1205. Ticker::from_str(&t).unwrap()
  1206. });
  1207. }
  1208. #[cfg(feature = "unstable")]
  1209. #[bench]
  1210. fn ticker_from_str_bench_catchall(b: &mut Bencher) {
  1211. let t = black_box(String::from("usd_zar"));
  1212. b.iter(||{
  1213. Ticker::from_str(&t).unwrap()
  1214. });
  1215. }
  1216. #[cfg(feature = "unstable")]
  1217. #[bench]
  1218. fn it_converts_ticker_to_u8(b: &mut Bencher) {
  1219. let t = black_box(t!(btc-usd));
  1220. b.iter(|| {
  1221. u8::from(black_box(t))
  1222. });
  1223. }
  1224. #[cfg(feature = "unstable")]
  1225. #[bench]
  1226. fn it_converts_ticker_u8_plus_base_and_quote(b: &mut Bencher) {
  1227. struct A {
  1228. pub ticker: i16,
  1229. pub base: i16,
  1230. pub quote: i16
  1231. }
  1232. let t = black_box(t!(btc-usd));
  1233. b.iter(|| {
  1234. let ticker = u8::from(black_box(t)) as i16;
  1235. let base = u8::from(black_box(t.base)) as i16;
  1236. let quote = u8::from(black_box(t.quote)) as i16;
  1237. A { ticker, base, quote }
  1238. });
  1239. }
  1240. #[test]
  1241. fn it_asserts_that_ticker_takes_up_two_bytes() {
  1242. assert_eq!(::std::mem::size_of::<Ticker>(), 2);
  1243. }
  1244. #[test]
  1245. fn it_checks_a_currency_to_str_return_value() {
  1246. assert_eq!(c!(btc).as_str(), "btc");
  1247. }
  1248. #[cfg(feature = "unstable")]
  1249. #[bench]
  1250. fn ticker_to_string(b: &mut Bencher) {
  1251. let ticker = t!(btc-usd);
  1252. b.iter(|| {
  1253. black_box(ticker.to_string())
  1254. });
  1255. }
  1256. #[cfg(feature = "unstable")]
  1257. #[bench]
  1258. fn ticker_to_s(b: &mut Bencher) {
  1259. let ticker = t!(btc-usd);
  1260. b.iter(|| {
  1261. black_box(ticker.to_s())
  1262. });
  1263. }
  1264. #[cfg(feature = "unstable")]
  1265. #[bench]
  1266. fn ticker_to_str(b: &mut Bencher) {
  1267. let ticker = t!(btc-usd);
  1268. b.iter(|| {
  1269. black_box(ticker.as_str())
  1270. });
  1271. }
  1272. #[cfg(feature = "unstable")]
  1273. #[bench]
  1274. fn currency_to_str(b: &mut Bencher) {
  1275. let usd_currency = c!(usd);
  1276. b.iter(|| {
  1277. usd_currency.as_str()
  1278. });
  1279. }
  1280. #[cfg(feature = "unstable")]
  1281. #[bench]
  1282. fn currency_to_str_uppercase(b: &mut Bencher) {
  1283. let usd_currency = c!(usd);
  1284. b.iter(|| {
  1285. usd_currency.to_str_uppercase()
  1286. });
  1287. }
  1288. }