Skip to main content

chaser_cf/core/
config.rs

1//! Configuration for chaser-cf
2
3use crate::models::Profile;
4use std::env;
5use std::path::PathBuf;
6use std::time::Duration;
7
8/// Configuration for ChaserCF
9#[derive(Debug, Clone)]
10pub struct ChaserConfig {
11    /// Maximum concurrent browser contexts (default: 20)
12    pub context_limit: usize,
13
14    /// Request timeout in milliseconds (default: 60000)
15    pub timeout_ms: u64,
16
17    /// Stealth profile to use (default: Windows)
18    pub profile: Profile,
19
20    /// Whether to defer browser initialization until first use (default: false)
21    pub lazy_init: bool,
22
23    /// Path to Chrome/Chromium binary (default: auto-detect)
24    pub chrome_path: Option<PathBuf>,
25
26    /// Whether to run in headless mode (default: false for stealth)
27    pub headless: bool,
28
29    /// Browser viewport width (default: 1920)
30    pub viewport_width: u32,
31
32    /// Browser viewport height (default: 1080)
33    pub viewport_height: u32,
34
35    /// Extra command-line flags to pass to the Chrome process, appended
36    /// to chaser-cf's defaults (`--disable-blink-features=Automation-
37    /// Controlled`, `--disable-infobars`).
38    ///
39    /// Common values:
40    /// - `--no-sandbox` — required when the host process runs as root
41    ///   (e.g. systemd unit, Docker containers without `--user`),
42    ///   otherwise Chrome refuses to start with the message
43    ///   "Running as root without --no-sandbox is not supported".
44    /// - `--disable-gpu` — for headless servers without a GPU.
45    /// - `--disable-dev-shm-usage` — for /dev/shm-constrained containers.
46    ///
47    /// Default: empty (chaser-cf only sets its own minimum baseline flags).
48    pub extra_args: Vec<String>,
49}
50
51impl Default for ChaserConfig {
52    fn default() -> Self {
53        Self {
54            context_limit: 20,
55            timeout_ms: 60000,
56            profile: Profile::Windows,
57            lazy_init: false,
58            chrome_path: None,
59            headless: false,
60            viewport_width: 1920,
61            viewport_height: 1080,
62            extra_args: Vec::new(),
63        }
64    }
65}
66
67impl ChaserConfig {
68    /// Create configuration from environment variables
69    ///
70    /// Environment variables:
71    /// - `CHASER_CONTEXT_LIMIT`: Max concurrent contexts (default: 20)
72    /// - `CHASER_TIMEOUT`: Timeout in ms (default: 60000)
73    /// - `CHASER_PROFILE`: Profile name (windows/linux/macos)
74    /// - `CHASER_LAZY_INIT`: Enable lazy init (true/false)
75    /// - `CHROME_BIN`: Path to Chrome binary
76    /// - `CHASER_HEADLESS`: Run headless (true/false)
77    /// - `CHASER_EXTRA_ARGS`: Whitespace-separated Chrome flags appended to
78    ///   chaser-cf's defaults (e.g. `--no-sandbox --disable-gpu`)
79    pub fn from_env() -> Self {
80        let mut config = Self::default();
81
82        if let Ok(val) = env::var("CHASER_CONTEXT_LIMIT") {
83            if let Ok(limit) = val.parse() {
84                config.context_limit = limit;
85            }
86        }
87
88        if let Ok(val) = env::var("CHASER_TIMEOUT") {
89            if let Ok(timeout) = val.parse() {
90                config.timeout_ms = timeout;
91            }
92        }
93
94        if let Ok(val) = env::var("CHASER_PROFILE") {
95            if let Some(profile) = Profile::parse(&val) {
96                config.profile = profile;
97            }
98        }
99
100        if let Ok(val) = env::var("CHASER_LAZY_INIT") {
101            config.lazy_init = val.eq_ignore_ascii_case("true") || val == "1";
102        }
103
104        if let Ok(val) = env::var("CHROME_BIN") {
105            config.chrome_path = Some(PathBuf::from(val));
106        }
107
108        if let Ok(val) = env::var("CHASER_HEADLESS") {
109            config.headless = val.eq_ignore_ascii_case("true") || val == "1";
110        }
111
112        if let Ok(val) = env::var("CHASER_EXTRA_ARGS") {
113            config.extra_args = val
114                .split_whitespace()
115                .map(|s| s.to_string())
116                .collect();
117        }
118
119        config
120    }
121
122    /// Builder method: set context limit
123    pub fn with_context_limit(mut self, limit: usize) -> Self {
124        self.context_limit = limit;
125        self
126    }
127
128    /// Builder method: set timeout
129    pub fn with_timeout_ms(mut self, timeout: u64) -> Self {
130        self.timeout_ms = timeout;
131        self
132    }
133
134    /// Builder method: set timeout from Duration
135    pub fn with_timeout(mut self, timeout: Duration) -> Self {
136        self.timeout_ms = timeout.as_millis() as u64;
137        self
138    }
139
140    /// Builder method: set profile
141    pub fn with_profile(mut self, profile: Profile) -> Self {
142        self.profile = profile;
143        self
144    }
145
146    /// Builder method: enable lazy initialization
147    pub fn with_lazy_init(mut self, lazy: bool) -> Self {
148        self.lazy_init = lazy;
149        self
150    }
151
152    /// Builder method: set Chrome path
153    pub fn with_chrome_path(mut self, path: impl Into<PathBuf>) -> Self {
154        self.chrome_path = Some(path.into());
155        self
156    }
157
158    /// Builder method: set headless mode
159    pub fn with_headless(mut self, headless: bool) -> Self {
160        self.headless = headless;
161        self
162    }
163
164    /// Builder method: set viewport size
165    pub fn with_viewport(mut self, width: u32, height: u32) -> Self {
166        self.viewport_width = width;
167        self.viewport_height = height;
168        self
169    }
170
171    /// Builder method: replace the extra Chrome args set with the given list.
172    /// Use [`Self::add_extra_arg`] to append a single flag instead.
173    pub fn with_extra_args<I, S>(mut self, args: I) -> Self
174    where
175        I: IntoIterator<Item = S>,
176        S: Into<String>,
177    {
178        self.extra_args = args.into_iter().map(Into::into).collect();
179        self
180    }
181
182    /// Builder method: append a single Chrome flag to the existing extras.
183    /// Useful for chaining, e.g.
184    /// `ChaserConfig::default().add_extra_arg("--no-sandbox")`.
185    pub fn add_extra_arg(mut self, arg: impl Into<String>) -> Self {
186        self.extra_args.push(arg.into());
187        self
188    }
189
190    /// Get timeout as Duration
191    pub fn timeout(&self) -> Duration {
192        Duration::from_millis(self.timeout_ms)
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_default_config() {
202        let config = ChaserConfig::default();
203        assert_eq!(config.context_limit, 20);
204        assert_eq!(config.timeout_ms, 60000);
205        assert_eq!(config.profile, Profile::Windows);
206        assert!(!config.lazy_init);
207        assert!(!config.headless);
208        assert!(config.extra_args.is_empty());
209    }
210
211    #[test]
212    fn test_with_extra_args_replaces() {
213        let config = ChaserConfig::default()
214            .with_extra_args(["--no-sandbox", "--disable-gpu"]);
215        assert_eq!(config.extra_args, vec!["--no-sandbox", "--disable-gpu"]);
216        // with_extra_args replaces; calling again clears the previous set
217        let config2 = config.with_extra_args(vec!["--foo"]);
218        assert_eq!(config2.extra_args, vec!["--foo"]);
219    }
220
221    #[test]
222    fn test_add_extra_arg_appends() {
223        let config = ChaserConfig::default()
224            .add_extra_arg("--no-sandbox")
225            .add_extra_arg("--disable-gpu");
226        assert_eq!(config.extra_args, vec!["--no-sandbox", "--disable-gpu"]);
227    }
228
229    #[test]
230    fn test_builder_pattern() {
231        let config = ChaserConfig::default()
232            .with_context_limit(10)
233            .with_timeout_ms(30000)
234            .with_profile(Profile::Linux)
235            .with_lazy_init(true)
236            .with_headless(true);
237
238        assert_eq!(config.context_limit, 10);
239        assert_eq!(config.timeout_ms, 30000);
240        assert_eq!(config.profile, Profile::Linux);
241        assert!(config.lazy_init);
242        assert!(config.headless);
243    }
244}