use crate::{
user::APIKey,
util::{ForexFunction, Interval, OutputSize},
};
use reqwest::Url;
use serde_derive::Deserialize;
use std::collections::HashMap;
const LINK: &str = "https://www.alphavantage.co/query?function=";
#[derive(Debug, Clone)]
struct MetaData {
information: String,
from_symbol: String,
to_symbol: String,
last_refreshed: String,
interval: Option<String>,
output_size: Option<String>,
time_zone: String,
}
#[derive(Default, Debug, Clone)]
pub struct Entry {
time: String,
open: String,
high: String,
low: String,
close: String,
}
impl Entry {
pub fn time(&self) -> String {
self.time.to_string()
}
pub fn open(&self) -> f64 {
return_f64(&self.open)
}
pub fn high(&self) -> f64 {
return_f64(&self.high)
}
pub fn low(&self) -> f64 {
return_f64(&self.low)
}
pub fn close(&self) -> f64 {
return_f64(&self.close)
}
}
fn return_f64(data: &str) -> f64 {
data.trim().parse::<f64>().unwrap()
}
#[derive(Debug, Default)]
pub struct Forex {
error_message: Option<String>,
information: Option<String>,
meta_data: Option<MetaData>,
forex: Option<Vec<Entry>>,
}
impl Forex {
pub fn information(&self) -> Result<String, String> {
self.return_meta_string("information")
}
pub fn symbol_from(&self) -> Result<String, String> {
self.return_meta_string("from symbol")
}
pub fn symbol_to(&self) -> Result<String, String> {
self.return_meta_string("to symbol")
}
pub fn last_refreshed(&self) -> Result<String, String> {
if let Some(meta) = &self.meta_data {
Ok(format!("{} {}", meta.last_refreshed, meta.time_zone))
} else if let Some(error) = &self.error_message {
Err(format!("Error Message : {}", error))
} else {
Err(format!(
"Information : {}",
self.information.clone().unwrap()
))
}
}
pub fn interval(&self) -> Result<String, String> {
self.operate_option_meta_value("interval")
}
pub fn output_size(&self) -> Result<String, String> {
self.operate_option_meta_value("output size")
}
pub fn entry(&self) -> Result<Vec<Entry>, String> {
if let Some(entry) = &self.forex {
Ok(entry.to_vec())
} else if let Some(error) = &self.error_message {
Err(format!("Error Message : {}", error))
} else {
Err(format!(
"Information : {}",
self.information.clone().unwrap()
))
}
}
fn return_meta_string(&self, which_val: &str) -> Result<String, String> {
if let Some(meta_data) = &self.meta_data {
let value = match which_val {
"information" => &meta_data.information,
"from symbol" => &meta_data.from_symbol,
"to symbol" => &meta_data.to_symbol,
_ => "",
};
Ok(value.to_string())
} else if let Some(error) = &self.error_message {
Err(format!("Error Message : {}", error))
} else {
Err(format!(
"Information : {}",
self.information.clone().unwrap()
))
}
}
fn operate_option_meta_value(&self, which_val: &str) -> Result<String, String> {
if let Some(meta_data) = &self.meta_data {
if let Some(value) = match which_val {
"interval" => &meta_data.interval,
"output size" => &meta_data.output_size,
_ => &None,
} {
Ok(value.to_string())
} else {
Err("No value present".to_string())
}
} else if let Some(error) = &self.error_message {
Err(format!("Error Message : {}", error))
} else {
Err(format!(
"Information : {}",
self.information.clone().unwrap()
))
}
}
}
#[derive(Clone, Debug, Deserialize)]
struct EntryHelper {
#[serde(rename = "1. open")]
open: String,
#[serde(rename = "2. high")]
high: String,
#[serde(rename = "3. low")]
low: String,
#[serde(rename = "4. close")]
close: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ForexHelper {
#[serde(rename = "Error Message")]
error_message: Option<String>,
#[serde(rename = "Information")]
information: Option<String>,
#[serde(rename = "Meta Data")]
meta_data: Option<HashMap<String, String>>,
#[serde(flatten)]
forex: Option<HashMap<String, HashMap<String, EntryHelper>>>,
}
impl ForexHelper {
pub(crate) fn convert(self) -> Forex {
let mut forex_struct = Forex::default();
forex_struct.error_message = self.error_message;
forex_struct.information = self.information;
if let Some(meta_data) = self.meta_data {
let information = &meta_data["1. Information"];
let from_symbol = &meta_data["2. From Symbol"];
let to_symbol = &meta_data["3. To Symbol"];
let last_refreshed = meta_data.get("4. Last Refreshed");
let mut last_refreshed_value = return_option_value(last_refreshed);
if last_refreshed_value.is_none() {
let last_refreshed = meta_data.get("5. Last Refreshed");
last_refreshed_value = return_option_value(last_refreshed);
}
let time_zone = meta_data.get("5. Time Zone");
let mut time_zone_value = return_option_value(time_zone);
if time_zone_value.is_none() {
let time_zone = meta_data.get("6. Time Zone");
time_zone_value = return_option_value(time_zone);
}
if time_zone_value.is_none() {
let time_zone = meta_data.get("7. Time Zone");
time_zone_value = return_option_value(time_zone);
}
let output_size = meta_data.get("4. Output Size");
let mut output_size_value = return_option_value(output_size);
if output_size_value.is_none() {
let output_size = meta_data.get("6. Output Size");
output_size_value = return_option_value(output_size);
}
let interval = meta_data.get("5. Interval");
let interval_value = return_option_value(interval);
forex_struct.meta_data = Some(MetaData {
information: information.to_string(),
from_symbol: from_symbol.to_string(),
to_symbol: to_symbol.to_string(),
last_refreshed: last_refreshed_value.unwrap(),
interval: interval_value,
output_size: output_size_value,
time_zone: time_zone_value.unwrap(),
});
}
let mut value: Vec<Entry> = Vec::new();
if let Some(entry) = self.forex {
for hash in entry.values() {
for val in hash.keys() {
let mut entry: Entry = crate::forex::Entry::default();
entry.time = val.to_string();
let entry_helper = hash.get(val).unwrap().clone();
entry.open = entry_helper.open;
entry.high = entry_helper.high;
entry.low = entry_helper.low;
entry.close = entry_helper.close;
value.push(entry);
}
}
}
if !value.is_empty() {
forex_struct.forex = Some(value);
}
forex_struct
}
}
fn return_option_value(value: Option<&std::string::String>) -> Option<String> {
match value {
Some(value) => Some(value.to_string()),
None => None,
}
}
pub fn forex(
function: ForexFunction,
from_symbol: &str,
to_symbol: &str,
interval: Interval,
output_size: OutputSize,
api_data: (&str, Option<u64>),
) -> Forex {
let api;
if let Some(timeout) = api_data.1 {
api = APIKey::set_with_timeout(api_data.0, timeout);
} else {
api = APIKey::set_api(api_data.0);
}
api.forex(function, from_symbol, to_symbol, interval, output_size)
}
pub(crate) fn create_url(
function: ForexFunction,
from_symbol: &str,
to_symbol: &str,
interval: Interval,
output_size: OutputSize,
api: &str,
) -> Url {
let function = match function {
ForexFunction::IntraDay => "FX_INTRADAY",
ForexFunction::Daily => "FX_DAILY",
ForexFunction::Weekly => "FX_WEEKLY",
ForexFunction::Monthly => "FX_MONTHLY",
};
let mut url = format!(
"{}{}&from_symbol={}&to_symbol={}",
LINK, function, from_symbol, to_symbol
);
let interval = match interval {
Interval::OneMin => "1min",
Interval::FiveMin => "5min",
Interval::FifteenMin => "15min",
Interval::ThirtyMin => "30min",
Interval::SixtyMin => "60min",
Interval::None => "",
};
if interval != "" {
url.push_str(format!("&interval={}", interval).as_str());
}
url.push_str(match output_size {
OutputSize::Full => "&outputsize=full",
_ => "",
});
url.push_str(format!("&apikey={}", api).as_str());
url.parse().unwrap()
}
#[cfg(test)]
mod test {
use crate::util::*;
use reqwest::Url;
#[test]
fn test_forex_create_url() {
assert_eq!(
super::create_url(
ForexFunction::Daily,
"USD",
"NPR",
Interval::None,
OutputSize::None,
"random"
),
Url::parse(
"https://www.alphavantage.co/query?function=FX_DAILY\
&from_symbol=USD\
&to_symbol=NPR\
&apikey=random"
)
.unwrap()
);
assert_eq!(
super::create_url(
ForexFunction::Weekly,
"USD",
"NPR",
Interval::None,
OutputSize::None,
"random"
),
Url::parse(
"https://www.alphavantage.co/query?function=FX_WEEKLY\
&from_symbol=USD\
&to_symbol=NPR\
&apikey=random"
)
.unwrap()
);
assert_eq!(
super::create_url(
ForexFunction::Monthly,
"USD",
"NPR",
Interval::None,
OutputSize::None,
"random"
),
Url::parse(
"https://www.alphavantage.co/query?function=FX_MONTHLY\
&from_symbol=USD\
&to_symbol=NPR\
&apikey=random"
)
.unwrap()
);
assert_eq!(
super::create_url(
ForexFunction::IntraDay,
"USD",
"NPR",
Interval::FifteenMin,
OutputSize::Full,
"random"
),
Url::parse(
"https://www.alphavantage.co/query?function=FX_INTRADAY\
&from_symbol=USD\
&to_symbol=NPR\
&interval=15min\
&outputsize=full\
&apikey=random"
)
.unwrap()
);
}
}