1#[cfg(not(any(target_os = "ios", target_os = "tvos", target_os = "watchos")))]
2use crate::CrashpadError;
3use crate::Result;
4use std::env;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone)]
9pub struct CrashpadConfig {
10 handler_path: PathBuf,
11 database_path: PathBuf,
12 metrics_path: PathBuf,
13 url: Option<String>,
14 handler_arguments: Vec<String>,
15}
16
17impl Default for CrashpadConfig {
18 fn default() -> Self {
19 let exe_dir = env::current_exe()
20 .ok()
21 .and_then(|p| p.parent().map(|p| p.to_path_buf()))
22 .unwrap_or_else(|| PathBuf::from("."));
23
24 Self {
25 handler_path: PathBuf::new(),
26 database_path: exe_dir.join("crashpad_db"),
27 metrics_path: exe_dir.join("crashpad_metrics"),
28 url: None,
29 handler_arguments: Vec::new(),
30 }
31 }
32}
33
34impl CrashpadConfig {
35 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn builder() -> CrashpadConfigBuilder {
42 CrashpadConfigBuilder::default()
43 }
44
45 pub fn with_database_path<P: AsRef<Path>>(mut self, path: P) -> Self {
47 self.database_path = path.as_ref().to_path_buf();
48 self
49 }
50
51 pub fn with_metrics_path<P: AsRef<Path>>(mut self, path: P) -> Self {
53 self.metrics_path = path.as_ref().to_path_buf();
54 self
55 }
56
57 pub fn with_url<S: Into<String>>(mut self, url: S) -> Self {
59 self.url = Some(url.into());
60 self
61 }
62
63 pub(crate) fn handler_path(&self) -> Result<PathBuf> {
71 #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos"))]
73 {
74 return Ok(PathBuf::new());
76 }
77
78 #[cfg(not(any(target_os = "ios", target_os = "tvos", target_os = "watchos")))]
79 {
80 let handler_name = if cfg!(target_os = "android") {
82 "libcrashpad_handler.so"
83 } else if cfg!(windows) {
84 "crashpad_handler.exe"
85 } else {
86 "crashpad_handler"
87 };
88
89 if !self.handler_path.as_os_str().is_empty() {
91 let path = &self.handler_path;
92 if path.exists() {
93 return Ok(path.clone());
94 }
95 return Ok(path.clone());
98 }
99
100 if let Ok(env_path) = env::var("CRASHPAD_HANDLER") {
102 let path = PathBuf::from(env_path);
103 if path.exists() {
104 return Ok(path);
105 }
106 }
107
108 if let Ok(exe_path) = env::current_exe() {
110 if let Some(exe_dir) = exe_path.parent() {
111 let handler_path = exe_dir.join(handler_name);
112 if handler_path.exists() {
113 return Ok(handler_path);
114 }
115 }
116 }
117
118 let cwd_handler = PathBuf::from(handler_name);
120 if cwd_handler.exists() {
121 return Ok(cwd_handler);
122 }
123
124 Err(CrashpadError::InvalidConfiguration(
125 format!(
126 "Handler '{handler_name}' not found. Searched: config path, CRASHPAD_HANDLER env, executable directory, current directory"
127 )
128 ))
129 }
130 }
131
132 pub(crate) fn database_path(&self) -> &Path {
133 &self.database_path
134 }
135
136 pub(crate) fn metrics_path(&self) -> &Path {
137 &self.metrics_path
138 }
139
140 pub(crate) fn url(&self) -> Option<&str> {
141 self.url.as_deref()
142 }
143
144 pub(crate) fn handler_arguments(&self) -> &[String] {
145 &self.handler_arguments
146 }
147}
148
149#[derive(Default)]
151pub struct CrashpadConfigBuilder {
152 config: CrashpadConfig,
153}
154
155impl CrashpadConfigBuilder {
156 pub fn handler_path<P: AsRef<Path>>(mut self, path: P) -> Self {
158 self.config.handler_path = path.as_ref().to_path_buf();
159 self
160 }
161
162 pub fn database_path<P: AsRef<Path>>(mut self, path: P) -> Self {
164 self.config.database_path = path.as_ref().to_path_buf();
165 self
166 }
167
168 pub fn metrics_path<P: AsRef<Path>>(mut self, path: P) -> Self {
170 self.config.metrics_path = path.as_ref().to_path_buf();
171 self
172 }
173
174 pub fn url<S: Into<String>>(mut self, url: S) -> Self {
176 self.config.url = Some(url.into());
177 self
178 }
179
180 pub fn rate_limit(mut self, enabled: bool) -> Self {
191 if !enabled {
192 self.config
193 .handler_arguments
194 .push("--no-rate-limit".to_string());
195 }
196 self
197 }
198
199 pub fn upload_gzip(mut self, enabled: bool) -> Self {
208 if !enabled {
209 self.config
210 .handler_arguments
211 .push("--no-upload-gzip".to_string());
212 }
213 self
214 }
215
216 pub fn periodic_tasks(mut self, enabled: bool) -> Self {
225 if !enabled {
226 self.config
227 .handler_arguments
228 .push("--no-periodic-tasks".to_string());
229 }
230 self
231 }
232
233 pub fn identify_client_via_url(mut self, enabled: bool) -> Self {
242 if !enabled {
243 self.config
244 .handler_arguments
245 .push("--no-identify-client-via-url".to_string());
246 }
247 self
248 }
249
250 pub fn handler_argument<S: Into<String>>(mut self, arg: S) -> Self {
264 self.config.handler_arguments.push(arg.into());
265 self
266 }
267
268 pub fn handler_arguments<I, S>(mut self, args: I) -> Self
282 where
283 I: IntoIterator<Item = S>,
284 S: Into<String>,
285 {
286 self.config
287 .handler_arguments
288 .extend(args.into_iter().map(Into::into));
289 self
290 }
291
292 pub fn build(self) -> CrashpadConfig {
294 self.config
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_builder() {
304 let config = CrashpadConfig::builder()
305 .handler_path("/usr/local/bin/crashpad_handler")
306 .database_path("/tmp/crashes")
307 .url("https://crashes.example.com")
308 .build();
309
310 assert_eq!(
311 config.handler_path.to_str().unwrap(),
312 "/usr/local/bin/crashpad_handler"
313 );
314 assert_eq!(config.database_path.to_str().unwrap(), "/tmp/crashes");
315 assert_eq!(config.url.as_deref(), Some("https://crashes.example.com"));
316 }
317
318 #[test]
319 #[cfg(not(any(target_os = "ios", target_os = "tvos", target_os = "watchos")))]
320 fn test_handler_path_fallback() {
321 let config = CrashpadConfig::builder()
323 .handler_path("/explicit/path/crashpad_handler")
324 .build();
325
326 assert_eq!(
328 config.handler_path().unwrap().to_str().unwrap(),
329 "/explicit/path/crashpad_handler"
330 );
331
332 let config = CrashpadConfig::builder().build();
334
335 let result = config.handler_path();
338
339 if let Ok(path) = result {
341 let filename = path.file_name().unwrap().to_str().unwrap();
342 assert!(
343 filename == "crashpad_handler"
344 || filename == "crashpad_handler.exe"
345 || filename == "libcrashpad_handler.so"
346 );
347 }
348 }
349
350 #[test]
351 #[cfg(not(any(target_os = "ios", target_os = "tvos", target_os = "watchos")))]
352 fn test_handler_env_var() {
353 let original = env::var("CRASHPAD_HANDLER").ok();
358
359 env::set_var("CRASHPAD_HANDLER", "/env/path/crashpad_handler");
361
362 let config = CrashpadConfig::builder().build();
363
364 let _result = config.handler_path();
367
368 if let Some(orig) = original {
370 env::set_var("CRASHPAD_HANDLER", orig);
371 } else {
372 env::remove_var("CRASHPAD_HANDLER");
373 }
374 }
375
376 #[test]
377 fn test_handler_arguments_high_level() {
378 let config = CrashpadConfig::builder()
380 .rate_limit(false)
381 .upload_gzip(false)
382 .periodic_tasks(false)
383 .identify_client_via_url(false)
384 .build();
385
386 assert!(config
387 .handler_arguments
388 .contains(&"--no-rate-limit".to_string()));
389 assert!(config
390 .handler_arguments
391 .contains(&"--no-upload-gzip".to_string()));
392 assert!(config
393 .handler_arguments
394 .contains(&"--no-periodic-tasks".to_string()));
395 assert!(config
396 .handler_arguments
397 .contains(&"--no-identify-client-via-url".to_string()));
398 }
399
400 #[test]
401 fn test_handler_arguments_low_level() {
402 let config = CrashpadConfig::builder()
404 .handler_argument("--monitor-self")
405 .handler_arguments(vec!["--arg1", "--arg2"])
406 .build();
407
408 assert!(config
409 .handler_arguments
410 .contains(&"--monitor-self".to_string()));
411 assert!(config.handler_arguments.contains(&"--arg1".to_string()));
412 assert!(config.handler_arguments.contains(&"--arg2".to_string()));
413 }
414
415 #[test]
416 fn test_handler_arguments_mixed() {
417 let config = CrashpadConfig::builder()
419 .rate_limit(false)
420 .handler_argument("--monitor-self")
421 .upload_gzip(true) .handler_argument("--monitor-self-annotation=test=value")
423 .build();
424
425 assert!(config
426 .handler_arguments
427 .contains(&"--no-rate-limit".to_string()));
428 assert!(config
429 .handler_arguments
430 .contains(&"--monitor-self".to_string()));
431 assert!(config
432 .handler_arguments
433 .contains(&"--monitor-self-annotation=test=value".to_string()));
434 assert!(!config
436 .handler_arguments
437 .contains(&"--no-upload-gzip".to_string()));
438 }
439
440 #[test]
441 fn test_handler_arguments_default() {
442 let config = CrashpadConfig::default();
444 assert!(config.handler_arguments.is_empty());
445 }
446}