use reqwest::header::HeaderMap;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::str::FromStr;
use std::string::ToString;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ExecutionEnvironment {
Demo,
Live,
}
impl<'de> Deserialize<'de> for ExecutionEnvironment {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?.to_uppercase();
match s.as_str() {
"DEMO" => Ok(ExecutionEnvironment::Demo),
"LIVE" => Ok(ExecutionEnvironment::Live),
_ => Err(serde::de::Error::custom("Invalid account type")),
}
}
}
impl FromStr for ExecutionEnvironment {
type Err = ApiError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"DEMO" => Ok(ExecutionEnvironment::Demo),
"LIVE" => Ok(ExecutionEnvironment::Live),
_ => Err(ApiError {
message: format!("Invalid account type: {}", s),
}),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ApiConfig {
pub account_number_demo: String,
pub account_number_live: String,
pub api_key: String,
pub auto_login: Option<bool>,
pub base_url_demo: String,
pub base_url_live: String,
pub execution_environment: ExecutionEnvironment,
pub password: String,
pub session_version: Option<usize>,
pub username: String,
}
impl Default for ApiConfig {
fn default() -> Self {
if !std::path::Path::new("config.yaml").exists() {
panic!("config.yaml file not found!");
}
let config_contents = fs::read_to_string("config.yaml").unwrap();
let config: HashMap<String, serde_yaml::Value> =
serde_yaml::from_str(&config_contents).unwrap();
let api_config_value = config
.get("ig_trading_api")
.expect("ig_trading_api value not found in config file!");
let api_config: ApiConfig = serde_yaml::from_value(api_config_value.clone()).unwrap();
api_config
}
}
#[derive(Debug)]
pub struct ApiError {
pub message: String,
}
impl std::fmt::Display for ApiError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "API error: {}", self.message)
}
}
impl std::error::Error for ApiError {}
pub fn deserialize<'de, T>(value: &'de Value) -> Result<T, Box<dyn Error>>
where
T: Deserialize<'de>,
{
let result = serde_path_to_error::deserialize(value);
match result {
Ok(value) => Ok(value),
Err(e) => Err(Box::new(ApiError {
message: format!("Failed to deserialize JSON serde_json::Value: {}", e),
})),
}
}
pub fn params_to_query_string(params: Option<HashMap<String, String>>) -> String {
let mut query_string = "".to_string();
if let Some(params) = params {
if !params.is_empty() {
if query_string.is_empty() {
query_string.push('?');
}
for (key, value) in params {
query_string.push_str(&format!("{}={}&", key, value));
}
query_string.pop();
}
}
query_string
}
pub fn params_to_json(params: Option<HashMap<String, String>>) -> Value {
let mut map = serde_json::Map::new();
if let Some(params) = params {
if !params.is_empty() {
for (key, value) in params {
map.insert(key, Value::String(value));
}
}
}
Value::Object(map)
}
pub fn headers_to_json(headers: &HeaderMap) -> Result<Value, Box<dyn Error>> {
let mut map = serde_json::Map::new();
for (key, value) in headers {
map.insert(
key.as_str().to_string(),
Value::String(value.to_str()?.to_string()),
);
}
Ok(Value::Object(map))
}