1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::env;
use std::{borrow::Cow, sync::Arc};

use crate::transports::DefaultTransportFactory;
use crate::types::Dsn;
use crate::{ClientOptions, Integration};

/// Apply default client options.
///
/// Extends the given `ClientOptions` with default options such as a default
/// transport, a set of default integrations if not requested otherwise, and
/// also sets the `dsn`, `release`, `environment`, and proxy settings based on
/// environment variables.
///
/// When the [`ClientOptions::default_integrations`] option is set to
/// `true` (the default), the following integrations will be added *before*
/// any manually defined integrations, depending on enabled feature flags:
///
/// 1. [`AttachStacktraceIntegration`] (`feature = "backtrace"`)
/// 2. [`DebugImagesIntegration`] (`feature = "debug-images"`)
/// 3. [`ContextIntegration`] (`feature = "contexts"`)
/// 4. [`PanicIntegration`] (`feature = "panic"`)
/// 5. [`ProcessStacktraceIntegration`] (`feature = "backtrace"`)
///
/// Some integrations can be used multiple times, however, the
/// [`PanicIntegration`] can not, and it will not pick up custom panic
/// extractors when it is defined multiple times.
///
/// # Examples
/// ```
/// std::env::set_var("SENTRY_RELEASE", "release-from-env");
///
/// let options = sentry::ClientOptions::default();
/// assert_eq!(options.release, None);
/// assert!(options.transport.is_none());
///
/// let options = sentry::apply_defaults(options);
/// assert_eq!(options.release, Some("release-from-env".into()));
/// assert!(options.transport.is_some());
/// ```
///
/// [`AttachStacktraceIntegration`]: integrations/backtrace/struct.AttachStacktraceIntegration.html
/// [`DebugImagesIntegration`]: integrations/debug_images/struct.DebugImagesIntegration.html
/// [`ContextIntegration`]: integrations/contexts/struct.ContextIntegration.html
/// [`PanicIntegration`]: integrations/panic/struct.PanicIntegration.html
/// [`ProcessStacktraceIntegration`]: integrations/backtrace/struct.ProcessStacktraceIntegration.html
pub fn apply_defaults(mut opts: ClientOptions) -> ClientOptions {
    if opts.transport.is_none() {
        opts.transport = Some(Arc::new(DefaultTransportFactory));
    }
    if opts.default_integrations {
        // default integrations need to be ordered *before* custom integrations,
        // since they also process events in order
        let mut integrations: Vec<Arc<dyn Integration>> = vec![];
        #[cfg(feature = "backtrace")]
        {
            integrations.push(Arc::new(
                sentry_backtrace::AttachStacktraceIntegration::default(),
            ));
        }
        #[cfg(feature = "debug-images")]
        {
            integrations.push(Arc::new(
                sentry_debug_images::DebugImagesIntegration::default(),
            ))
        }
        #[cfg(feature = "contexts")]
        {
            integrations.push(Arc::new(sentry_contexts::ContextIntegration::default()));
        }
        #[cfg(feature = "panic")]
        {
            integrations.push(Arc::new(sentry_panic::PanicIntegration::default()));
        }
        #[cfg(feature = "backtrace")]
        {
            integrations.push(Arc::new(
                sentry_backtrace::ProcessStacktraceIntegration::default(),
            ));
        }
        integrations.extend(opts.integrations.into_iter());
        opts.integrations = integrations;
    }
    if opts.dsn.is_none() {
        opts.dsn = env::var("SENTRY_DSN")
            .ok()
            .and_then(|dsn| dsn.parse::<Dsn>().ok());
    }
    if opts.release.is_none() {
        opts.release = env::var("SENTRY_RELEASE").ok().map(Cow::Owned);
    }
    if opts.environment.is_none() {
        opts.environment =
            env::var("SENTRY_ENVIRONMENT")
                .ok()
                .map(Cow::Owned)
                .or(Some(Cow::Borrowed(if cfg!(debug_assertions) {
                    "development"
                } else {
                    "production"
                })));
    }
    if opts.http_proxy.is_none() {
        opts.http_proxy = std::env::var("HTTP_PROXY")
            .ok()
            .map(Cow::Owned)
            .or_else(|| std::env::var("http_proxy").ok().map(Cow::Owned));
    }
    if opts.https_proxy.is_none() {
        opts.https_proxy = std::env::var("HTTPS_PROXY")
            .ok()
            .map(Cow::Owned)
            .or_else(|| std::env::var("https_proxy").ok().map(Cow::Owned))
            .or_else(|| opts.http_proxy.clone());
    }
    if let Ok(accept_invalid_certs) = std::env::var("SSL_VERIFY") {
        opts.accept_invalid_certs = !accept_invalid_certs.parse().unwrap_or(true);
    }
    opts
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_environment() {
        let opts = ClientOptions {
            environment: Some("explicit-env".into()),
            ..Default::default()
        };
        let opts = apply_defaults(opts);
        assert_eq!(opts.environment.unwrap(), "explicit-env");

        let opts = apply_defaults(Default::default());
        // I doubt anyone runs test code without debug assertions
        assert_eq!(opts.environment.unwrap(), "development");

        env::set_var("SENTRY_ENVIRONMENT", "env-from-env");
        let opts = apply_defaults(Default::default());
        assert_eq!(opts.environment.unwrap(), "env-from-env");
    }
}