1use std::collections::HashMap;
18
19use serde::{Deserialize, Serialize};
20
21#[derive(Serialize, Deserialize, Debug)]
32pub struct Subdivision {
33 pub exponent: i8,
35 pub name: Option<String>,
37}
38
39#[derive(Serialize, Deserialize, Debug)]
41pub struct CurrencyInfo {
42 pub alphabetic_code: String,
44 pub name: String,
46 pub numeric_code: Option<u16>,
48 pub symbol: Option<String>,
50 pub standards_entities: Vec<String>,
53 pub subdivisions: Vec<Subdivision>,
55}
56
57lazy_static! {
62 static ref CURRENCIES: HashMap<String, CurrencyInfo> = load_currencies_from_json();
63 static ref NUMERIC_LOOKUP: HashMap<u16, String> = make_currency_lookup();
64}
65
66pub fn lookup_by_alpha(alphabetic_code: &str) -> Option<&'static CurrencyInfo> {
67 assert_eq!(
68 alphabetic_code.len(),
69 3,
70 "currency code must be 3 characters long"
71 );
72 CURRENCIES.get(alphabetic_code)
73}
74
75pub fn lookup_by_numeric(numeric_code: &u16) -> Option<&'static CurrencyInfo> {
76 match NUMERIC_LOOKUP.get(&numeric_code) {
77 Some(v) => lookup_by_alpha(v),
78 None => None,
79 }
80}
81
82pub fn currency_alpha_codes() -> Vec<String> {
83 CURRENCIES.keys().cloned().collect()
84}
85
86pub fn currency_numeric_codes() -> Vec<u16> {
87 NUMERIC_LOOKUP.keys().cloned().collect()
88}
89
90pub fn currencies_for_country_name(name: &str) -> Vec<&'static CurrencyInfo> {
91 CURRENCIES
92 .values()
93 .filter(|currency| currency.standards_entities.contains(&name.to_string()))
94 .collect()
95}
96
97pub fn all_alpha_codes() -> Vec<String> {
98 CURRENCIES.keys().cloned().collect()
99}
100
101pub fn all_numeric_codes() -> Vec<u16> {
102 NUMERIC_LOOKUP.keys().cloned().collect()
103}
104
105fn load_currencies_from_json() -> HashMap<String, CurrencyInfo> {
110 info!("currencies_from_json - loading JSON");
111 let raw_data = include_bytes!("data/currencies.json");
112 let currency_map: HashMap<String, CurrencyInfo> = serde_json::from_slice(raw_data).unwrap();
113 info!(
114 "currencies_from_json - loaded {} currencies",
115 currency_map.len()
116 );
117 currency_map
118}
119
120fn make_currency_lookup() -> HashMap<u16, String> {
121 info!("load_currency_lookup - create from CURRENCIES");
122 let mut lookup_map: HashMap<u16, String> = HashMap::new();
123 for currency in CURRENCIES.values() {
124 if let Some(numeric) = ¤cy.numeric_code {
125 lookup_map.insert(*numeric, currency.alphabetic_code.to_string());
126 }
127 }
128 info!(
129 "load_currency_lookup - mapped {} countries",
130 lookup_map.len()
131 );
132 lookup_map
133}
134
135#[cfg(test)]
140mod tests {
141 use super::*;
142
143 use serde_json::ser::to_string_pretty;
144
145 #[test]
147 fn test_currency_loading() {
148 match lookup_by_alpha(&"GBP".to_string()) {
149 None => println!("lookup_by_alpha NO 'GBP'"),
150 Some(c) => println!("lookup_by_alpha {:#?}", to_string_pretty(c)),
151 }
152 }
153
154 #[test]
156 fn test_currency_codes() {
157 let codes = currency_alpha_codes();
158 assert!(codes.len() > 0);
159 let numerics = currency_numeric_codes();
160 assert!(numerics.len() > 0);
161 }
162
163 #[test]
164 fn test_good_currency_code() {
165 match lookup_by_alpha("GBP") {
166 None => panic!("was expecting a currency"),
167 Some(currency) => assert_eq!(currency.name.to_string(), "Pound Sterling".to_string()),
168 }
169 }
170
171 #[test]
172 fn test_bad_currency_code() {
173 match lookup_by_alpha(&"ZZZ") {
174 None => (),
175 Some(_) => panic!("was expecting a None in response"),
176 }
177 }
178
179 #[test]
180 fn test_for_country() {
181 let currencies = currencies_for_country_name("Mexico");
182 assert_eq!(currencies.len(), 2);
183 }
184}