truefix_ig_client/
config.rs1use std::time::Duration;
2
3use crate::error::{IgError, IgResult};
4
5#[derive(Clone, PartialEq, Eq)]
7pub struct Credentials {
8 identifier: String,
9 password: String,
10 api_key: String,
11}
12
13impl Credentials {
14 pub fn new(
16 identifier: impl Into<String>,
17 password: impl Into<String>,
18 api_key: impl Into<String>,
19 ) -> IgResult<Self> {
20 let credentials = Self {
21 identifier: identifier.into(),
22 password: password.into(),
23 api_key: api_key.into(),
24 };
25 if credentials.identifier.is_empty()
26 || credentials.password.is_empty()
27 || credentials.api_key.is_empty()
28 {
29 return Err(IgError::InvalidConfiguration(
30 "identifier, password, and API key must be non-empty".to_owned(),
31 ));
32 }
33 Ok(credentials)
34 }
35
36 pub(crate) fn identifier(&self) -> &str {
37 &self.identifier
38 }
39 pub(crate) fn password(&self) -> &str {
40 &self.password
41 }
42 pub(crate) fn api_key(&self) -> &str {
43 &self.api_key
44 }
45}
46
47impl std::fmt::Debug for Credentials {
48 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 formatter.write_str("Credentials(REDACTED)")
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct LiveTradingConfirmation(());
56
57impl LiveTradingConfirmation {
58 pub const fn acknowledge_risk() -> Self {
60 Self(())
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Default)]
66pub enum Environment {
67 #[default]
69 Demo,
70 Live(LiveTradingConfirmation),
72 Custom { rest_base: String },
74}
75
76impl Environment {
77 const DEMO_REST_BASE: &str = "https://demo-api.ig.com/gateway/deal";
78 const LIVE_REST_BASE: &str = "https://api.ig.com/gateway/deal";
79
80 pub fn rest_base(&self) -> &str {
82 match self {
83 Self::Demo => Self::DEMO_REST_BASE,
84 Self::Live(_) => Self::LIVE_REST_BASE,
85 Self::Custom { rest_base } => rest_base,
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Default)]
92pub enum AuthenticationVersion {
93 #[default]
95 V2,
96 V3 { account_id: String },
98}
99
100#[derive(Debug, Clone)]
102pub struct ClientConfig {
103 pub environment: Environment,
104 pub credentials: Option<Credentials>,
105 pub authentication: AuthenticationVersion,
106 pub timeout: Duration,
107 pub proxy: Option<String>,
108}
109
110impl Default for ClientConfig {
111 fn default() -> Self {
112 Self {
113 environment: Environment::Demo,
114 credentials: None,
115 authentication: AuthenticationVersion::V2,
116 timeout: Duration::from_secs(15),
117 proxy: None,
118 }
119 }
120}
121
122impl ClientConfig {
123 pub fn demo(credentials: Option<Credentials>) -> Self {
125 Self {
126 credentials,
127 ..Self::default()
128 }
129 }
130
131 pub fn live(credentials: Credentials, confirmation: LiveTradingConfirmation) -> Self {
133 Self {
134 environment: Environment::Live(confirmation),
135 credentials: Some(credentials),
136 ..Self::default()
137 }
138 }
139
140 pub fn with_v3_authentication(mut self, account_id: impl Into<String>) -> IgResult<Self> {
142 let account_id = account_id.into();
143 if account_id.is_empty() {
144 return Err(IgError::InvalidConfiguration(
145 "v3 authentication requires a non-empty account ID".to_owned(),
146 ));
147 }
148 self.authentication = AuthenticationVersion::V3 { account_id };
149 Ok(self)
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn credentials_redact_debug_output() {
159 let credentials = Credentials::new("user", "secret", "key").unwrap();
160 assert!(!format!("{credentials:?}").contains("secret"));
161 }
162
163 #[test]
164 fn demo_is_default() {
165 assert_eq!(ClientConfig::default().environment, Environment::Demo);
166 }
167
168 #[test]
169 fn v3_requires_an_account_id() {
170 assert!(ClientConfig::default().with_v3_authentication("").is_err());
171 assert_eq!(
172 ClientConfig::default()
173 .with_v3_authentication("ABC123")
174 .unwrap()
175 .authentication,
176 AuthenticationVersion::V3 {
177 account_id: "ABC123".to_owned(),
178 }
179 );
180 }
181}