use std::time::Duration;
use crate::error::{IgError, IgResult};
#[derive(Clone, PartialEq, Eq)]
pub struct Credentials {
identifier: String,
password: String,
api_key: String,
}
impl Credentials {
pub fn new(
identifier: impl Into<String>,
password: impl Into<String>,
api_key: impl Into<String>,
) -> IgResult<Self> {
let credentials = Self {
identifier: identifier.into(),
password: password.into(),
api_key: api_key.into(),
};
if credentials.identifier.is_empty()
|| credentials.password.is_empty()
|| credentials.api_key.is_empty()
{
return Err(IgError::InvalidConfiguration(
"identifier, password, and API key must be non-empty".to_owned(),
));
}
Ok(credentials)
}
pub(crate) fn identifier(&self) -> &str {
&self.identifier
}
pub(crate) fn password(&self) -> &str {
&self.password
}
pub(crate) fn api_key(&self) -> &str {
&self.api_key
}
}
impl std::fmt::Debug for Credentials {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("Credentials(REDACTED)")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LiveTradingConfirmation(());
impl LiveTradingConfirmation {
pub const fn acknowledge_risk() -> Self {
Self(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum Environment {
#[default]
Demo,
Live(LiveTradingConfirmation),
Custom { rest_base: String },
}
impl Environment {
const DEMO_REST_BASE: &str = "https://demo-api.ig.com/gateway/deal";
const LIVE_REST_BASE: &str = "https://api.ig.com/gateway/deal";
pub fn rest_base(&self) -> &str {
match self {
Self::Demo => Self::DEMO_REST_BASE,
Self::Live(_) => Self::LIVE_REST_BASE,
Self::Custom { rest_base } => rest_base,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum AuthenticationVersion {
#[default]
V2,
V3 { account_id: String },
}
#[derive(Debug, Clone)]
pub struct ClientConfig {
pub environment: Environment,
pub credentials: Option<Credentials>,
pub authentication: AuthenticationVersion,
pub timeout: Duration,
pub proxy: Option<String>,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
environment: Environment::Demo,
credentials: None,
authentication: AuthenticationVersion::V2,
timeout: Duration::from_secs(15),
proxy: None,
}
}
}
impl ClientConfig {
pub fn demo(credentials: Option<Credentials>) -> Self {
Self {
credentials,
..Self::default()
}
}
pub fn live(credentials: Credentials, confirmation: LiveTradingConfirmation) -> Self {
Self {
environment: Environment::Live(confirmation),
credentials: Some(credentials),
..Self::default()
}
}
pub fn with_v3_authentication(mut self, account_id: impl Into<String>) -> IgResult<Self> {
let account_id = account_id.into();
if account_id.is_empty() {
return Err(IgError::InvalidConfiguration(
"v3 authentication requires a non-empty account ID".to_owned(),
));
}
self.authentication = AuthenticationVersion::V3 { account_id };
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn credentials_redact_debug_output() {
let credentials = Credentials::new("user", "secret", "key").unwrap();
assert!(!format!("{credentials:?}").contains("secret"));
}
#[test]
fn demo_is_default() {
assert_eq!(ClientConfig::default().environment, Environment::Demo);
}
#[test]
fn v3_requires_an_account_id() {
assert!(ClientConfig::default().with_v3_authentication("").is_err());
assert_eq!(
ClientConfig::default()
.with_v3_authentication("ABC123")
.unwrap()
.authentication,
AuthenticationVersion::V3 {
account_id: "ABC123".to_owned(),
}
);
}
}