ig_client/application/
config.rs

1use crate::constants::{DAYS_TO_BACK_LOOK, DEFAULT_PAGE_SIZE, DEFAULT_SLEEP_TIME};
2use crate::storage::config::DatabaseConfig;
3use crate::utils::config::get_env_or_default;
4use dotenv::dotenv;
5use pretty_simple_display::{DebugPretty, DisplaySimple};
6use serde::{Deserialize, Serialize};
7use sqlx::postgres::PgPoolOptions;
8use std::env;
9use tracing::error;
10use tracing::log::debug;
11
12#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone)]
13/// Authentication credentials for the IG Markets API
14pub struct Credentials {
15    /// Username for the IG Markets account
16    pub username: String,
17    /// Password for the IG Markets account
18    pub password: String,
19    /// Account ID for the IG Markets account
20    pub account_id: String,
21    /// API key for the IG Markets API
22    pub api_key: String,
23    /// Client token for the IG Markets API
24    pub client_token: Option<String>,
25    /// Account token for the IG Markets API
26    pub account_token: Option<String>,
27}
28
29#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone)]
30/// Main configuration for the IG Markets API client
31pub struct Config {
32    /// Authentication credentials
33    pub credentials: Credentials,
34    /// REST API configuration
35    pub rest_api: RestApiConfig,
36    /// WebSocket API configuration
37    pub websocket: WebSocketConfig,
38    /// Database configuration for data persistence
39    pub database: DatabaseConfig,
40    /// Rate limiter configuration for API requests
41    pub rate_limiter: RateLimiterConfig,
42    /// Number of hours between transaction fetching operations
43    pub sleep_hours: u64,
44    /// Number of items to retrieve per page in API requests
45    pub page_size: u32,
46    /// Number of days to look back when fetching historical data
47    pub days_to_look_back: i64,
48    /// API version to use for authentication (2 or 3). If None, auto-detect based on available tokens
49    pub api_version: Option<u8>,
50}
51
52#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone)]
53/// Configuration for the REST API
54pub struct RestApiConfig {
55    /// Base URL for the IG Markets REST API
56    pub base_url: String,
57    /// Timeout in seconds for REST API requests
58    pub timeout: u64,
59}
60
61#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone)]
62/// Configuration for the WebSocket API
63pub struct WebSocketConfig {
64    /// URL for the IG Markets WebSocket API
65    pub url: String,
66    /// Reconnect interval in seconds for WebSocket connections
67    pub reconnect_interval: u64,
68}
69
70#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone)]
71/// Configuration for rate limiting API requests
72pub struct RateLimiterConfig {
73    /// Maximum number of requests allowed per period
74    pub max_requests: u32,
75    /// Time period in seconds for the rate limit
76    pub period_seconds: u64,
77    /// Burst size - maximum number of requests that can be made at once
78    pub burst_size: u32,
79}
80
81impl Default for Config {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87impl Config {
88    /// Creates a new configuration instance with a specific rate limit type
89    ///
90    /// # Arguments
91    ///
92    /// * `rate_limit_type` - The type of rate limit to enforce
93    /// * `safety_margin` - A value between 0.0 and 1.0 representing the percentage of the actual limit to use
94    ///
95    /// # Returns
96    ///
97    /// A new `Config` instance
98    pub fn new() -> Self {
99        // Explicitly load the .env file
100        match dotenv() {
101            Ok(_) => debug!("Successfully loaded .env file"),
102            Err(e) => debug!("Failed to load .env file: {e}"),
103        }
104
105        // Check if environment variables are configured
106        let username = get_env_or_default("IG_USERNAME", String::from("default_username"));
107        let password = get_env_or_default("IG_PASSWORD", String::from("default_password"));
108        let api_key = get_env_or_default("IG_API_KEY", String::from("default_api_key"));
109        let sleep_hours = get_env_or_default("TX_LOOP_INTERVAL_HOURS", DEFAULT_SLEEP_TIME);
110        let page_size = get_env_or_default("TX_PAGE_SIZE", DEFAULT_PAGE_SIZE);
111        let days_to_look_back = get_env_or_default("TX_DAYS_LOOKBACK", DAYS_TO_BACK_LOOK);
112
113        // Check if we are using default values
114        if username == "default_username" {
115            error!("IG_USERNAME not found in environment variables or .env file");
116        }
117        if password == "default_password" {
118            error!("IG_PASSWORD not found in environment variables or .env file");
119        }
120        if api_key == "default_api_key" {
121            error!("IG_API_KEY not found in environment variables or .env file");
122        }
123
124        Config {
125            credentials: Credentials {
126                username,
127                password,
128                account_id: get_env_or_default("IG_ACCOUNT_ID", String::from("default_account_id")),
129                api_key,
130                client_token: None,
131                account_token: None,
132            },
133            rest_api: RestApiConfig {
134                base_url: get_env_or_default(
135                    "IG_REST_BASE_URL",
136                    String::from("https://demo-api.ig.com/gateway/deal"),
137                ),
138                timeout: get_env_or_default("IG_REST_TIMEOUT", 30),
139            },
140            websocket: WebSocketConfig {
141                url: get_env_or_default(
142                    "IG_WS_URL",
143                    String::from("wss://demo-apd.marketdatasystems.com"),
144                ),
145                reconnect_interval: get_env_or_default("IG_WS_RECONNECT_INTERVAL", 5),
146            },
147            database: DatabaseConfig {
148                url: get_env_or_default(
149                    "DATABASE_URL",
150                    String::from("postgres://postgres:postgres@localhost/ig"),
151                ),
152                max_connections: get_env_or_default("DATABASE_MAX_CONNECTIONS", 5),
153            },
154            rate_limiter: RateLimiterConfig {
155                max_requests: get_env_or_default("IG_RATE_LIMIT_MAX_REQUESTS", 4), // 3
156                period_seconds: get_env_or_default("IG_RATE_LIMIT_PERIOD_SECONDS", 12), // 10
157                burst_size: get_env_or_default("IG_RATE_LIMIT_BURST_SIZE", 3),
158            },
159            sleep_hours,
160            page_size,
161            days_to_look_back,
162            api_version: env::var("IG_API_VERSION")
163                .ok()
164                .and_then(|v| v.parse::<u8>().ok())
165                .filter(|&v| v == 2 || v == 3)
166                .or(Some(3)), // Default to API v3 (OAuth) if not specified
167        }
168    }
169
170    /// Creates a PostgreSQL connection pool using the database configuration
171    ///
172    /// # Returns
173    ///
174    /// A Result containing either a PostgreSQL connection pool or an error
175    pub async fn pg_pool(&self) -> Result<sqlx::Pool<sqlx::Postgres>, sqlx::Error> {
176        PgPoolOptions::new()
177            .max_connections(self.database.max_connections)
178            .connect(&self.database.url)
179            .await
180    }
181}