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.

crypto.rs 46KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525
  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. $(
  602. #[doc = $name]
  603. $ticker = $code
  604. ),*
  605. }
  606. impl Exchange {
  607. pub fn name(&self) -> &'static str {
  608. match *self {
  609. $(
  610. e!($ticker) => { $name }
  611. ),*
  612. }
  613. }
  614. pub fn as_str(&self) -> &'static str {
  615. match *self {
  616. $(
  617. e!($ticker) => { stringify!($ticker) }
  618. ),*
  619. }
  620. }
  621. pub fn to_str_uppercase(&self) -> &'static str {
  622. match *self {
  623. $(
  624. e!($ticker) => { stringify!($upper) }
  625. ),*
  626. }
  627. }
  628. pub fn all() -> Vec<Self> {
  629. vec![
  630. $( e!($ticker) ),*
  631. ]
  632. }
  633. pub fn variant_str_slice() -> &'static [&'static str] {
  634. &[
  635. $(
  636. stringify!($ticker)
  637. ),*
  638. ]
  639. }
  640. #[allow(non_upper_case_globals)]
  641. pub fn from_bytes<'a>(bytes: &'a [u8]) -> Result<Self, UnlistedExchange<'a>> {
  642. // this declares for each currency the equivalent of
  643. // const BTC : &[u8] = "btc".as_bytes();
  644. $(
  645. const $upper: &[u8] = stringify!($ticker).as_bytes();
  646. )*
  647. // this declares for each currency the equivalent of
  648. // const btc : &[u8] = "BTC".as_bytes();
  649. $(
  650. const $ticker: &[u8] = stringify!($upper).as_bytes();
  651. )*
  652. match bytes {
  653. // first try lowercase (identified by uppercase consts)
  654. $(
  655. $upper => { return Ok(Exchange::$ticker) }
  656. )*
  657. // then try uppercase (identified by lowercase consts)
  658. $(
  659. $ticker => { return Ok(Exchange::$ticker) }
  660. )*
  661. other => Err(UnlistedExchange(std::str::from_utf8(other).unwrap_or("<utf8 error>")))
  662. }
  663. }
  664. /// Optimized for expecting lowercase. Does not attempt to search beyond
  665. /// every currency symbol in lowercase.
  666. ///
  667. pub fn from_str_lowercase<'a>(s: &'a str) -> Result<Self, UnlistedExchange<'a>> {
  668. match s {
  669. $(
  670. stringify!($ticker) => { Ok(Exchange::$ticker) }
  671. )*
  672. other => Err(UnlistedExchange(other))
  673. }
  674. }
  675. /// Optimized for expecting uppercase. Does not attempt to search beyond
  676. /// every currency symbol in uppercase.
  677. ///
  678. pub fn from_str_uppercase<'a>(s: &'a str) -> Result<Self, UnlistedExchange<'a>> {
  679. match s {
  680. $(
  681. stringify!($upper) => { Ok(Exchange::$ticker) }
  682. )*
  683. other => Err(UnlistedExchange(other))
  684. }
  685. }
  686. }
  687. impl TryFrom<u8> for Exchange {
  688. type Error = Error;
  689. fn try_from(n: u8) -> Result<Self, Self::Error> {
  690. match n {
  691. $(
  692. $code => { Ok(e!($ticker)) }
  693. ),*
  694. _ => Err(Error::IllegalExchangeCode(n))
  695. }
  696. }
  697. }
  698. impl From<Exchange> for u8 {
  699. fn from(c: Exchange) -> Self {
  700. match c {
  701. $(
  702. e!($ticker) => { $code }
  703. ),*
  704. }
  705. }
  706. }
  707. impl FromStr for Exchange {
  708. type Err = Error;
  709. fn from_str(s: &str) -> Result<Self, Self::Err> {
  710. let t = match s.to_lowercase().trim() {
  711. $(
  712. stringify!($ticker) => { e!($ticker) }
  713. ),*
  714. _ => {
  715. return Err(Error::IllegalExchange);
  716. }
  717. };
  718. Ok(t)
  719. }
  720. }
  721. /// Holds an object of the same type for each exchange.
  722. ///
  723. pub struct ByExchange<T> {
  724. $(
  725. pub $ticker: T
  726. ),*
  727. }
  728. impl<T> ByExchange<T> {
  729. pub fn new(
  730. $(
  731. $ticker: T
  732. ),*
  733. ) -> Self {
  734. Self {
  735. $(
  736. $ticker
  737. ),*
  738. }
  739. }
  740. pub fn of(&self, exchange: &Exchange) -> &T {
  741. match exchange {
  742. $(
  743. &e!($ticker) => &self.$ticker
  744. ),*
  745. }
  746. }
  747. pub fn of_mut(&mut self, exchange: &Exchange) -> &mut T {
  748. match exchange {
  749. $(
  750. &e!($ticker) => &mut self.$ticker
  751. ),*
  752. }
  753. }
  754. // to match map apis:
  755. pub fn get(&self, exchange: &Exchange) -> Option<&T> {
  756. Some(self.of(exchange))
  757. }
  758. pub fn get_mut(&mut self, exchange: &Exchange) -> Option<&mut T> {
  759. Some(self.of_mut(exchange))
  760. }
  761. }
  762. impl<T> Default for ByExchange<T>
  763. where T: Default
  764. {
  765. fn default() -> Self {
  766. ByExchange {
  767. $(
  768. $ticker: Default::default()
  769. ),*
  770. }
  771. }
  772. }
  773. impl<T> Clone for ByExchange<T>
  774. where T: Clone
  775. {
  776. fn clone(&self) -> Self {
  777. ByExchange {
  778. $(
  779. $ticker: self.$ticker.clone()
  780. ),*
  781. }
  782. }
  783. }
  784. #[test]
  785. fn it_verifies_exch_lower_and_upper_consistency() {
  786. for x in Exchange::all() {
  787. assert_eq!(x.as_str().to_uppercase(), x.to_str_uppercase().to_string());
  788. }
  789. }
  790. #[test]
  791. fn checks_from_bytes_for_lower_and_upper_tickers_exch() {
  792. $(
  793. assert_eq!(
  794. Exchange::from_bytes(Exchange::$ticker.as_str().as_bytes()).unwrap(),
  795. Exchange::$ticker
  796. );
  797. assert_eq!(
  798. Exchange::from_bytes(Exchange::$ticker.to_str_uppercase().as_bytes()).unwrap(),
  799. Exchange::$ticker
  800. );
  801. )*
  802. }
  803. #[test]
  804. fn check_serde_json_deser_exch() {
  805. #[derive(Serialize, Deserialize, PartialEq, Debug)]
  806. struct A {
  807. pub exch: Exchange,
  808. }
  809. $(
  810. {
  811. let a = A { exch: Exchange::$ticker };
  812. let to_json = serde_json::to_string(&a).unwrap();
  813. let manual_json = format!("{{\"exch\":\"{}\"}}", stringify!($ticker));
  814. let manual_json_uppercase = format!("{{\"exch\":\"{}\"}}", stringify!($upper));
  815. assert_eq!(a, serde_json::from_str(&to_json).unwrap());
  816. assert_eq!(a, serde_json::from_str(&manual_json).unwrap());
  817. assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap());
  818. assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap());
  819. assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap());
  820. }
  821. )*
  822. }
  823. }
  824. }
  825. make_exchange!(
  826. | plnx, "Poloniex", 1, PLNX,
  827. | krkn, "Kraken", 2, KRKN,
  828. | gdax, "GDAX", 3, GDAX,
  829. | exmo, "Exmo", 4, EXMO,
  830. | bits, "Bitstamp", 5, BITS,
  831. | bmex, "Bitmex", 6, BMEX,
  832. | btfx, "Bitfinex", 7, BTFX,
  833. | bnce, "Binance", 8, BNCE,
  834. | okex, "OKEx", 9, OKEX,
  835. | drbt, "Deribit", 10, DRBT
  836. );
  837. #[test]
  838. fn check_generated_exchange_fns() {
  839. assert_eq!(e!(plnx), Exchange::plnx);
  840. assert_eq!(e!(plnx).name(), "Poloniex");
  841. assert_eq!(Exchange::try_from(2_u8).unwrap(), e!(krkn));
  842. assert_eq!(u8::from(e!(gdax)), 3_u8);
  843. }
  844. impl Display for Exchange {
  845. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  846. write!(f, "{}", self.as_str())
  847. }
  848. }
  849. #[derive(Debug, Clone, Hash, PartialEq, Eq, Copy, PartialOrd, Ord)]
  850. pub struct Ticker {
  851. pub base: Currency,
  852. pub quote: Currency,
  853. }
  854. impl Ticker {
  855. pub fn flip(&self) -> Self {
  856. Ticker { base: self.quote, quote: self.base }
  857. }
  858. pub fn to_s(&self) -> String {
  859. format!("{}", self)
  860. }
  861. }
  862. macro_rules! ticker_to_u8 {
  863. ($(|$base:tt-$quote:tt, $u_base:tt-$u_quote:tt, $n:expr),*) => {
  864. impl From<Ticker> for u8 {
  865. fn from(t: Ticker) -> Self {
  866. match t {
  867. $(
  868. t!($base-$quote) => { $n }
  869. ),*
  870. _ => 0
  871. }
  872. }
  873. }
  874. impl TryFrom<u8> for Ticker {
  875. type Error = Error;
  876. fn try_from(n: u8) -> Result<Self, Self::Error> {
  877. match n {
  878. $(
  879. $n => { Ok(t!($base-$quote)) }
  880. ),*
  881. other => Err(Error::IllegalValue(other))
  882. }
  883. }
  884. }
  885. impl Ticker {
  886. pub fn as_str(&self) -> &'static str {
  887. match *self {
  888. $(
  889. t!($base-$quote) => { concat!(stringify!($base), "_", stringify!($quote)) }
  890. ),*
  891. _ => "xxx_xxx"
  892. }
  893. }
  894. pub fn as_str_dash(&self) -> &'static str {
  895. match *self {
  896. $( t!($base-$quote) => { concat!(stringify!($base), "-", stringify!($quote)) } ),*
  897. _ => "xxx-xxx"
  898. }
  899. }
  900. pub fn to_str_uppercase(&self) -> &'static str {
  901. match *self {
  902. $(
  903. t!($base-$quote) => { concat!(stringify!($u_base), "_", stringify!($u_quote)) }
  904. ),*
  905. _ => "XXX_XXX"
  906. }
  907. }
  908. pub fn to_str_reversed(&self) -> &'static str {
  909. match *self {
  910. $(
  911. t!($base-$quote) => { concat!(stringify!($quote), "_", stringify!($base)) }
  912. ),*
  913. _ => "xxx_xxx"
  914. }
  915. }
  916. pub fn to_str_uppercase_reversed(&self) -> &'static str {
  917. match *self {
  918. $(
  919. t!($base-$quote) => { concat!(stringify!($u_quote), "_", stringify!($u_base)) }
  920. ),*
  921. _ => "XXX_XXX"
  922. }
  923. }
  924. /// t!(btc-usd) -> "BTC-USD"
  925. ///
  926. pub fn to_str_uppercase_dash_sep(&self) -> &'static str {
  927. match *self {
  928. $(
  929. t!($base-$quote) => { concat!(stringify!($u_base), "-", stringify!($u_quote)) }
  930. ),*
  931. _ => "XXX-XXX"
  932. }
  933. }
  934. pub fn as_gdax_str(&self) -> &'static str { self.to_str_uppercase_dash_sep() }
  935. /// Note - this performs the flip and fails for tickers not in the list.
  936. ///
  937. pub fn from_str_uppercase_reverse<'a>(s: &'a str) -> Result<Ticker, UnlistedTicker<'a>> {
  938. match s {
  939. $(
  940. concat!(stringify!($u_quote), "_", stringify!($u_base)) => { Ok(t!($base-$quote)) }
  941. )*
  942. other => Err(UnlistedTicker(other))
  943. }
  944. }
  945. pub fn from_str_uppercase_dash_sep(s: &str) -> Result<Ticker, Error> {
  946. match s {
  947. $(
  948. concat!(stringify!($u_base), "-", stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  949. )*
  950. other => ticker_parse_last_resort(other)
  951. }
  952. }
  953. pub fn from_str_bitfinex(s: &str) -> Result<Ticker, Error> {
  954. match s {
  955. $(
  956. concat!(stringify!($u_base), stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  957. )*
  958. other => ticker_parse_last_resort(other)
  959. }
  960. }
  961. pub fn from_str_bitmex(s: &str) -> Result<Ticker, Error> {
  962. match s {
  963. "XBTUSD" => Ok(t!(btc-usd)),
  964. "ETHUSD" => Ok(t!(eth-usd)),
  965. $(
  966. concat!(stringify!($u_base), stringify!($u_quote)) => { Ok(t!($base-$quote)) }
  967. )*
  968. other => ticker_parse_last_resort(other)
  969. }
  970. }
  971. }
  972. fn ticker_parse_last_resort(other: &str) -> Result<Ticker, Error> {
  973. match other.find('_').or_else(|| other.find('-')).or_else(|| other.find('/')) {
  974. Some(sep) if sep < other.len() - 1 => {
  975. let base = Currency::from_str(&other[..sep])?;
  976. let quote = Currency::from_str(&other[sep + 1..])?;
  977. Ok(Ticker { base, quote })
  978. }
  979. _ => Err(Error::IllegalFormat(Box::new(format!("failed to parse Ticker '{}'", other))))
  980. }
  981. }
  982. impl FromStr for Ticker {
  983. type Err = Error;
  984. fn from_str(s: &str) -> Result<Self, Error> {
  985. match s {
  986. $(
  987. concat!(stringify!($base), "_", stringify!($quote))
  988. //| concat!(stringify!($base), "-", stringify!($quote))
  989. //| concat!(stringify!($base), "/", stringify!($quote))
  990. //| concat!(stringify!($u_base), "_", stringify!($u_quote))
  991. //| concat!(stringify!($u_base), "-", stringify!($u_quote))
  992. //| concat!(stringify!($u_base), "-", stringify!($u_quote))
  993. => { Ok(t!($base-$quote)) }
  994. )*
  995. other => ticker_parse_last_resort(other)
  996. }
  997. }
  998. }
  999. #[test]
  1000. fn it_verifies_ticker_lower_and_upper_consistency() {
  1001. let tickers = vec![ $(t!($base-$quote)),*];
  1002. for t in &tickers {
  1003. assert_eq!(t.as_str().to_uppercase(), t.to_str_uppercase().to_string());
  1004. }
  1005. }
  1006. #[test]
  1007. fn check_serde_json_deser_ticker() {
  1008. #[derive(Serialize, Deserialize, PartialEq, Debug)]
  1009. struct A {
  1010. pub ticker: Ticker,
  1011. }
  1012. $(
  1013. {
  1014. let a = A { ticker: Ticker { base: Currency::$base, quote: Currency::$quote } };
  1015. let to_json = serde_json::to_string(&a).unwrap();
  1016. assert_eq!(a, serde_json::from_str(&to_json).unwrap());
  1017. for sep in ["_", "-", "/"].iter() {
  1018. let manual_json = format!("{{\"ticker\":\"{}{}{}\"}}", stringify!($base), sep, stringify!($quote));
  1019. let manual_json_uppercase = format!("{{\"ticker\":\"{}{}{}\"}}", stringify!($u_base), sep, stringify!($u_quote));
  1020. assert_eq!(a, serde_json::from_str(&manual_json).unwrap());
  1021. assert_eq!(a, serde_json::from_str(&manual_json_uppercase).unwrap());
  1022. assert_eq!(a, serde_json::from_slice(manual_json.as_bytes()).unwrap());
  1023. assert_eq!(a, serde_json::from_slice(manual_json_uppercase.as_bytes()).unwrap());
  1024. }
  1025. }
  1026. )*
  1027. }
  1028. }
  1029. }
  1030. ticker_to_u8!(
  1031. |btc-usd, BTC-USD, 1,
  1032. |xmr-usd, XMR-USD, 2,
  1033. |eth-usd, ETH-USD, 3,
  1034. |ltc-usd, LTC-USD, 4,
  1035. |etc-usd, ETC-USD, 5,
  1036. |xrp-usd, XRP-USD, 6,
  1037. |bch-usd, BCH-USD, 7,
  1038. |zec-usd, ZEC-USD, 8,
  1039. |dash-usd, DASH-USD, 9,
  1040. |rep-usd, REP-USD, 10,
  1041. |btc-usdt, BTC-USDT, 11,
  1042. |xmr-usdt, XMR-USDT, 12,
  1043. |eth-usdt, ETH-USDT, 13,
  1044. |ltc-usdt, LTC-USDT, 14,
  1045. |etc-usdt, ETC-USDT, 15,
  1046. |xrp-usdt, XRP-USDT, 16,
  1047. |bch-usdt, BCH-USDT, 17,
  1048. |zec-usdt, ZEC-USDT, 18,
  1049. |dash-usdt,DASH-USDT,19,
  1050. |rep-usdt, REP-USDT, 20,
  1051. |xmr-btc, XMR-BTC, 21,
  1052. |eth-btc, ETH-BTC, 22,
  1053. |ltc-btc, LTC-BTC, 23,
  1054. |etc-btc, ETC-BTC, 24,
  1055. |xrp-btc, XRP-BTC, 25,
  1056. |bch-btc, BCH-BTC, 26,
  1057. |zec-btc, ZEC-BTC, 27,
  1058. |dash-btc, DASH-BTC, 28,
  1059. |rep-btc, REP-BTC, 29,
  1060. |zec-eth, ZEC-ETH, 30,
  1061. |etc-eth, ETC-ETH, 31,
  1062. |bch-eth, BCH-ETH, 32,
  1063. |rep-eth, REP-ETH, 33,
  1064. |btc-eur, BTC-EUR, 34,
  1065. |btc-jpy, BTC-JPY, 35,
  1066. |btc-gbp, BTC-GBP, 36,
  1067. |btc-cad, BTC-CAD, 37,
  1068. |eth-eur, ETH-EUR, 38,
  1069. |eth-jpy, ETH-JPY, 39,
  1070. |eth-gbp, ETH-GBP, 40,
  1071. |eth-cad, ETH-CAD, 41,
  1072. |ltc-eur, LTC-EUR, 42,
  1073. |ltc-jpy, LTC-JPY, 43,
  1074. |ltc-gbp, LTC-GBP, 44,
  1075. |ltc-cad, LTC-CAD, 45,
  1076. |xmr-eur, XMR-EUR, 46,
  1077. |bch-eur, BCH-EUR, 47,
  1078. |rep-eur, REP-EUR, 48,
  1079. |etc-eur, ETC-EUR, 49,
  1080. |usdt-usd, USDT-USD, 50
  1081. // Note: a trailing comma will throw off the whole thing.
  1082. );
  1083. #[test]
  1084. fn check_generated_ticker_fns() {
  1085. assert_eq!(t!(btc-usd), Ticker { base: Currency::btc, quote: Currency::usd });
  1086. assert_eq!(t!(btc-usd).as_str(), "btc_usd");
  1087. assert_eq!(t!(eth-jpy).as_str(), "eth_jpy");
  1088. assert_eq!(t!(usd-btc).as_str(), "xxx_xxx");
  1089. assert_eq!(u8::from(t!(btc-usd)), 1_u8);
  1090. assert_eq!(u8::from(t!(btc-cad)), 37_u8);
  1091. assert_eq!(Ticker::try_from(1_u8).unwrap(), t!(btc-usd));
  1092. assert_eq!(Ticker::try_from(21_u8).unwrap(), t!(xmr-btc));
  1093. assert!(Ticker::try_from(121_u8).is_err());
  1094. }
  1095. impl Into<String> for Ticker {
  1096. fn into(self) -> String {
  1097. format!("{}_{}", self.base, self.quote)
  1098. }
  1099. }
  1100. impl From<(Currency, Currency)> for Ticker {
  1101. fn from(basequote: (Currency, Currency)) -> Self {
  1102. let (base, quote) = basequote;
  1103. Ticker { base, quote }
  1104. }
  1105. }
  1106. impl Serialize for Ticker {
  1107. fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
  1108. where S: Serializer
  1109. {
  1110. serializer.serialize_str(&format!("{}_{}", self.base, self.quote))
  1111. }
  1112. }
  1113. impl Display for Ticker {
  1114. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1115. write!(f, "{}_{}", self.base, self.quote)
  1116. }
  1117. }
  1118. #[derive(Debug, Clone)]
  1119. pub enum Error {
  1120. IllegalCurrency,
  1121. IllegalExchange,
  1122. IllegalValue(u8),
  1123. IllegalFormat(Box<String>),
  1124. IllegalExchangeCode(u8),
  1125. NaN,
  1126. }
  1127. impl Display for Error {
  1128. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1129. write!(f, "{:?}", self)
  1130. }
  1131. }
  1132. impl ::std::error::Error for Error {
  1133. fn description(&self) -> &str { "MoneyError" }
  1134. }
  1135. #[derive(Debug)]
  1136. pub struct UnlistedTicker<'a>(pub &'a str);
  1137. impl<'a> Display for UnlistedTicker<'a> {
  1138. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1139. write!(f, "UnlistedTicker({})", self.0)
  1140. }
  1141. }
  1142. #[derive(Debug)]
  1143. pub struct UnlistedCurrency<'a>(&'a str);
  1144. impl<'a> Display for UnlistedCurrency<'a> {
  1145. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1146. write!(f, "UnlistedCurrency({})", self.0)
  1147. }
  1148. }
  1149. #[derive(Debug)]
  1150. pub struct UnlistedExchange<'a>(&'a str);
  1151. impl<'a> Display for UnlistedExchange<'a> {
  1152. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1153. write!(f, "UnlistedExchange({})", self.0)
  1154. }
  1155. }
  1156. #[allow(unused)]
  1157. #[cfg(test)]
  1158. mod tests {
  1159. use super::*;
  1160. #[cfg(feature = "unstable")]
  1161. use test::{black_box, Bencher};
  1162. #[test]
  1163. fn ticker_from_str_advanced() {
  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("BTC_USD").unwrap(), t!(btc-usd));
  1168. assert_eq!(Ticker::from_str("BTC-USD").unwrap(), t!(btc-usd));
  1169. assert_eq!(Ticker::from_str("BTC/USD").unwrap(), t!(btc-usd));
  1170. assert_eq!(Ticker::from_str("USD_BTC").unwrap(), t!(usd-btc));
  1171. assert!(Ticker::from_str("not_a_ticker_format").is_err());
  1172. }
  1173. #[test]
  1174. fn it_parses_bcn_ticker() {
  1175. let ticker = Ticker::from_str("bcn_btc").unwrap();
  1176. }
  1177. #[test]
  1178. fn it_checks_simple_macro_use() {
  1179. let btc_cur = c!(btc);
  1180. assert_eq!(btc_cur, Currency::btc);
  1181. let plnx_exch = e!(plnx);
  1182. assert_eq!(plnx_exch, Exchange::plnx);
  1183. let eth_usd_ticker = t!(eth-usd);
  1184. assert_eq!(eth_usd_ticker.base, c!(eth));
  1185. assert_eq!(eth_usd_ticker.quote, c!(usd));
  1186. }
  1187. #[cfg(feature = "unstable")]
  1188. #[bench]
  1189. fn ticker_to_str_obfuscated(b: &mut Bencher) {
  1190. let ticker = Ticker::from_str("btc_usd").unwrap();
  1191. b.iter(|| {
  1192. black_box(ticker.as_str())
  1193. });
  1194. }
  1195. #[cfg(feature = "unstable")]
  1196. #[bench]
  1197. fn ticker_from_str_bench(b: &mut Bencher) {
  1198. let t = black_box(String::from("btc_usd"));
  1199. b.iter(||{
  1200. Ticker::from_str(&t).unwrap()
  1201. });
  1202. }
  1203. #[cfg(feature = "unstable")]
  1204. #[bench]
  1205. fn ticker_from_str_bench_last(b: &mut Bencher) {
  1206. let t = black_box(String::from("ltc_cad"));
  1207. b.iter(||{
  1208. Ticker::from_str(&t).unwrap()
  1209. });
  1210. }
  1211. #[cfg(feature = "unstable")]
  1212. #[bench]
  1213. fn ticker_from_str_bench_catchall(b: &mut Bencher) {
  1214. let t = black_box(String::from("usd_zar"));
  1215. b.iter(||{
  1216. Ticker::from_str(&t).unwrap()
  1217. });
  1218. }
  1219. #[cfg(feature = "unstable")]
  1220. #[bench]
  1221. fn it_converts_ticker_to_u8(b: &mut Bencher) {
  1222. let t = black_box(t!(btc-usd));
  1223. b.iter(|| {
  1224. u8::from(black_box(t))
  1225. });
  1226. }
  1227. #[cfg(feature = "unstable")]
  1228. #[bench]
  1229. fn it_converts_ticker_u8_plus_base_and_quote(b: &mut Bencher) {
  1230. struct A {
  1231. pub ticker: i16,
  1232. pub base: i16,
  1233. pub quote: i16
  1234. }
  1235. let t = black_box(t!(btc-usd));
  1236. b.iter(|| {
  1237. let ticker = u8::from(black_box(t)) as i16;
  1238. let base = u8::from(black_box(t.base)) as i16;
  1239. let quote = u8::from(black_box(t.quote)) as i16;
  1240. A { ticker, base, quote }
  1241. });
  1242. }
  1243. #[test]
  1244. fn it_asserts_that_ticker_takes_up_two_bytes() {
  1245. assert_eq!(::std::mem::size_of::<Ticker>(), 2);
  1246. }
  1247. #[test]
  1248. fn it_checks_a_currency_to_str_return_value() {
  1249. assert_eq!(c!(btc).as_str(), "btc");
  1250. }
  1251. #[cfg(feature = "unstable")]
  1252. #[bench]
  1253. fn ticker_to_string(b: &mut Bencher) {
  1254. let ticker = t!(btc-usd);
  1255. b.iter(|| {
  1256. black_box(ticker.to_string())
  1257. });
  1258. }
  1259. #[cfg(feature = "unstable")]
  1260. #[bench]
  1261. fn ticker_to_s(b: &mut Bencher) {
  1262. let ticker = t!(btc-usd);
  1263. b.iter(|| {
  1264. black_box(ticker.to_s())
  1265. });
  1266. }
  1267. #[cfg(feature = "unstable")]
  1268. #[bench]
  1269. fn ticker_to_str(b: &mut Bencher) {
  1270. let ticker = t!(btc-usd);
  1271. b.iter(|| {
  1272. black_box(ticker.as_str())
  1273. });
  1274. }
  1275. #[cfg(feature = "unstable")]
  1276. #[bench]
  1277. fn currency_to_str(b: &mut Bencher) {
  1278. let usd_currency = c!(usd);
  1279. b.iter(|| {
  1280. usd_currency.as_str()
  1281. });
  1282. }
  1283. #[cfg(feature = "unstable")]
  1284. #[bench]
  1285. fn currency_to_str_uppercase(b: &mut Bencher) {
  1286. let usd_currency = c!(usd);
  1287. b.iter(|| {
  1288. usd_currency.to_str_uppercase()
  1289. });
  1290. }
  1291. }