1use humantime::parse_duration;
2use secure_string::SecureString;
3use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
4use serde::{Deserialize, Deserializer};
5use std::fmt::Formatter;
6use std::time::Duration;
7use strut_factory::impl_deserialize_field;
8
9#[derive(Debug, Clone, PartialEq)]
15pub struct SentryConfig {
16 dsn: SecureString,
17 debug: bool,
18 sample_rate: f32,
19 traces_sample_rate: f32,
20 max_breadcrumbs: usize,
21 attach_stacktrace: bool,
22 shutdown_timeout: Duration,
23}
24
25impl SentryConfig {
26 pub fn dsn(&self) -> &SecureString {
29 &self.dsn
30 }
31
32 pub fn debug(&self) -> bool {
37 self.debug
38 }
39
40 pub fn sample_rate(&self) -> f32 {
45 self.sample_rate
46 }
47
48 pub fn traces_sample_rate(&self) -> f32 {
54 self.traces_sample_rate
55 }
56
57 pub fn max_breadcrumbs(&self) -> usize {
63 self.max_breadcrumbs
64 }
65
66 pub fn attach_stacktrace(&self) -> bool {
71 self.attach_stacktrace
72 }
73
74 pub fn shutdown_timeout(&self) -> Duration {
80 self.shutdown_timeout
81 }
82}
83
84impl Default for SentryConfig {
85 fn default() -> Self {
86 Self {
87 dsn: Self::default_dsn(),
88 debug: Self::default_debug(),
89 sample_rate: Self::default_sample_rate(),
90 traces_sample_rate: Self::default_traces_sample_rate(),
91 max_breadcrumbs: Self::default_max_breadcrumbs(),
92 attach_stacktrace: Self::default_attach_stacktrace(),
93 shutdown_timeout: Self::default_shutdown_timeout(),
94 }
95 }
96}
97
98impl SentryConfig {
99 fn default_dsn() -> SecureString {
100 "".into()
101 }
102
103 fn default_debug() -> bool {
104 false
105 }
106
107 fn default_sample_rate() -> f32 {
108 1.0
109 }
110
111 fn default_traces_sample_rate() -> f32 {
112 0.0
113 }
114
115 fn default_max_breadcrumbs() -> usize {
116 64
117 }
118
119 fn default_attach_stacktrace() -> bool {
120 false
121 }
122
123 fn default_shutdown_timeout() -> Duration {
124 Duration::from_secs(2)
125 }
126}
127
128impl AsRef<SentryConfig> for SentryConfig {
129 fn as_ref(&self) -> &SentryConfig {
130 self
131 }
132}
133
134const _: () = {
135 impl<'de> Deserialize<'de> for SentryConfig {
136 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
137 where
138 D: Deserializer<'de>,
139 {
140 deserializer.deserialize_any(SentryConfigVisitor)
141 }
142 }
143
144 struct SentryConfigVisitor;
145
146 impl<'de> Visitor<'de> for SentryConfigVisitor {
147 type Value = SentryConfig;
148
149 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
150 formatter.write_str("a map of Sentry integration configuration or a string Sentry DSN")
151 }
152
153 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
154 where
155 E: Error,
156 {
157 Ok(SentryConfig {
158 dsn: SecureString::from(value),
159 ..SentryConfig::default()
160 })
161 }
162
163 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
164 where
165 E: Error,
166 {
167 Ok(SentryConfig {
168 dsn: SecureString::from(value),
169 ..SentryConfig::default()
170 })
171 }
172
173 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
174 where
175 A: MapAccess<'de>,
176 {
177 let mut dsn = None;
178 let mut debug = None;
179 let mut sample_rate = None;
180 let mut traces_sample_rate = None;
181 let mut max_breadcrumbs = None;
182 let mut attach_stacktrace = None;
183 let mut shutdown_timeout = None;
184
185 while let Some(key) = map.next_key()? {
186 match key {
187 SentryConfigField::dsn => key.poll(&mut map, &mut dsn)?,
188 SentryConfigField::debug => key.poll(&mut map, &mut debug)?,
189 SentryConfigField::sample_rate => key.poll(&mut map, &mut sample_rate)?,
190 SentryConfigField::traces_sample_rate => {
191 key.poll(&mut map, &mut traces_sample_rate)?
192 }
193 SentryConfigField::max_breadcrumbs => {
194 key.poll(&mut map, &mut max_breadcrumbs)?
195 }
196 SentryConfigField::attach_stacktrace => {
197 key.poll(&mut map, &mut attach_stacktrace)?
198 }
199 SentryConfigField::shutdown_timeout => {
200 let duration_string = map.next_value::<String>()?;
201 let duration = parse_duration(&duration_string).map_err(Error::custom)?;
202 shutdown_timeout = Some(duration);
203 IgnoredAny
204 }
205 SentryConfigField::__ignore => map.next_value()?,
206 };
207 }
208
209 Ok(SentryConfig {
210 dsn: dsn.unwrap_or_else(SentryConfig::default_dsn),
211 debug: debug.unwrap_or_else(SentryConfig::default_debug),
212 sample_rate: sample_rate.unwrap_or_else(SentryConfig::default_sample_rate),
213 traces_sample_rate: traces_sample_rate
214 .unwrap_or_else(SentryConfig::default_traces_sample_rate),
215 max_breadcrumbs: max_breadcrumbs
216 .unwrap_or_else(SentryConfig::default_max_breadcrumbs),
217 attach_stacktrace: attach_stacktrace
218 .unwrap_or_else(SentryConfig::default_attach_stacktrace),
219 shutdown_timeout: shutdown_timeout
220 .unwrap_or_else(SentryConfig::default_shutdown_timeout),
221 })
222 }
223 }
224
225 impl_deserialize_field!(
226 SentryConfigField,
227 strut_deserialize::Slug::eq_as_slugs,
228 dsn,
229 debug,
230 sample_rate,
231 traces_sample_rate,
232 max_breadcrumbs,
233 attach_stacktrace,
234 shutdown_timeout,
235 );
236};
237
238#[cfg(test)]
239mod tests {
240 use crate::SentryConfig;
241 use pretty_assertions::assert_eq;
242 use secure_string::SecureString;
243 use std::time::Duration;
244
245 #[test]
246 fn from_empty() {
247 let input = "{}";
249 let expected_output = SentryConfig::default();
250
251 let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
253
254 assert_eq!(expected_output, actual_output);
256 }
257
258 #[test]
259 fn from_string() {
260 let input = "some_dsn";
262 let expected_output = SentryConfig {
263 dsn: SecureString::from("some_dsn"),
264 ..SentryConfig::default()
265 };
266
267 let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
269
270 assert_eq!(expected_output, actual_output);
272 }
273
274 #[test]
275 fn from_map_sparse() {
276 let input = r#"
278dsn: some_dsn
279"#;
280 let expected_output = SentryConfig {
281 dsn: SecureString::from("some_dsn"),
282 ..SentryConfig::default()
283 };
284
285 let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
287
288 assert_eq!(expected_output, actual_output);
290 }
291
292 #[test]
293 fn from_map_full() {
294 let input = r#"
296dsn: some_dsn
297debug: true
298sample_rate: 0.5
299traces_sample_rate: 0.4
300max_breadcrumbs: 50
301attach_stacktrace: true
302shutdown_timeout: 1s 500ms
303"#;
304 let expected_output = SentryConfig {
305 dsn: SecureString::from("some_dsn"),
306 debug: true,
307 sample_rate: 0.5,
308 traces_sample_rate: 0.4,
309 max_breadcrumbs: 50,
310 attach_stacktrace: true,
311 shutdown_timeout: Duration::from_millis(1500),
312 };
313
314 let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
316
317 assert_eq!(expected_output, actual_output);
319 }
320}