use humantime::parse_duration;
use secure_string::SecureString;
use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
use std::fmt::Formatter;
use std::time::Duration;
use strut_factory::impl_deserialize_field;
#[derive(Debug, Clone, PartialEq)]
pub struct SentryConfig {
dsn: SecureString,
debug: bool,
sample_rate: f32,
traces_sample_rate: f32,
max_breadcrumbs: usize,
attach_stacktrace: bool,
shutdown_timeout: Duration,
}
impl SentryConfig {
pub fn dsn(&self) -> &SecureString {
&self.dsn
}
pub fn debug(&self) -> bool {
self.debug
}
pub fn sample_rate(&self) -> f32 {
self.sample_rate
}
pub fn traces_sample_rate(&self) -> f32 {
self.traces_sample_rate
}
pub fn max_breadcrumbs(&self) -> usize {
self.max_breadcrumbs
}
pub fn attach_stacktrace(&self) -> bool {
self.attach_stacktrace
}
pub fn shutdown_timeout(&self) -> Duration {
self.shutdown_timeout
}
}
impl Default for SentryConfig {
fn default() -> Self {
Self {
dsn: Self::default_dsn(),
debug: Self::default_debug(),
sample_rate: Self::default_sample_rate(),
traces_sample_rate: Self::default_traces_sample_rate(),
max_breadcrumbs: Self::default_max_breadcrumbs(),
attach_stacktrace: Self::default_attach_stacktrace(),
shutdown_timeout: Self::default_shutdown_timeout(),
}
}
}
impl SentryConfig {
fn default_dsn() -> SecureString {
"".into()
}
fn default_debug() -> bool {
false
}
fn default_sample_rate() -> f32 {
1.0
}
fn default_traces_sample_rate() -> f32 {
0.0
}
fn default_max_breadcrumbs() -> usize {
64
}
fn default_attach_stacktrace() -> bool {
false
}
fn default_shutdown_timeout() -> Duration {
Duration::from_secs(2)
}
}
impl AsRef<SentryConfig> for SentryConfig {
fn as_ref(&self) -> &SentryConfig {
self
}
}
const _: () = {
impl<'de> Deserialize<'de> for SentryConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(SentryConfigVisitor)
}
}
struct SentryConfigVisitor;
impl<'de> Visitor<'de> for SentryConfigVisitor {
type Value = SentryConfig;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a map of Sentry integration configuration or a string Sentry DSN")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(SentryConfig {
dsn: SecureString::from(value),
..SentryConfig::default()
})
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: Error,
{
Ok(SentryConfig {
dsn: SecureString::from(value),
..SentryConfig::default()
})
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut dsn = None;
let mut debug = None;
let mut sample_rate = None;
let mut traces_sample_rate = None;
let mut max_breadcrumbs = None;
let mut attach_stacktrace = None;
let mut shutdown_timeout = None;
while let Some(key) = map.next_key()? {
match key {
SentryConfigField::dsn => key.poll(&mut map, &mut dsn)?,
SentryConfigField::debug => key.poll(&mut map, &mut debug)?,
SentryConfigField::sample_rate => key.poll(&mut map, &mut sample_rate)?,
SentryConfigField::traces_sample_rate => {
key.poll(&mut map, &mut traces_sample_rate)?
}
SentryConfigField::max_breadcrumbs => {
key.poll(&mut map, &mut max_breadcrumbs)?
}
SentryConfigField::attach_stacktrace => {
key.poll(&mut map, &mut attach_stacktrace)?
}
SentryConfigField::shutdown_timeout => {
let duration_string = map.next_value::<String>()?;
let duration = parse_duration(&duration_string).map_err(Error::custom)?;
shutdown_timeout = Some(duration);
IgnoredAny
}
SentryConfigField::__ignore => map.next_value()?,
};
}
Ok(SentryConfig {
dsn: dsn.unwrap_or_else(SentryConfig::default_dsn),
debug: debug.unwrap_or_else(SentryConfig::default_debug),
sample_rate: sample_rate.unwrap_or_else(SentryConfig::default_sample_rate),
traces_sample_rate: traces_sample_rate
.unwrap_or_else(SentryConfig::default_traces_sample_rate),
max_breadcrumbs: max_breadcrumbs
.unwrap_or_else(SentryConfig::default_max_breadcrumbs),
attach_stacktrace: attach_stacktrace
.unwrap_or_else(SentryConfig::default_attach_stacktrace),
shutdown_timeout: shutdown_timeout
.unwrap_or_else(SentryConfig::default_shutdown_timeout),
})
}
}
impl_deserialize_field!(
SentryConfigField,
strut_deserialize::Slug::eq_as_slugs,
dsn,
debug,
sample_rate,
traces_sample_rate,
max_breadcrumbs,
attach_stacktrace,
shutdown_timeout,
);
};
#[cfg(test)]
mod tests {
use crate::SentryConfig;
use pretty_assertions::assert_eq;
use secure_string::SecureString;
use std::time::Duration;
#[test]
fn from_empty() {
let input = "{}";
let expected_output = SentryConfig::default();
let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
assert_eq!(expected_output, actual_output);
}
#[test]
fn from_string() {
let input = "some_dsn";
let expected_output = SentryConfig {
dsn: SecureString::from("some_dsn"),
..SentryConfig::default()
};
let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
assert_eq!(expected_output, actual_output);
}
#[test]
fn from_map_sparse() {
let input = r#"
dsn: some_dsn
"#;
let expected_output = SentryConfig {
dsn: SecureString::from("some_dsn"),
..SentryConfig::default()
};
let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
assert_eq!(expected_output, actual_output);
}
#[test]
fn from_map_full() {
let input = r#"
dsn: some_dsn
debug: true
sample_rate: 0.5
traces_sample_rate: 0.4
max_breadcrumbs: 50
attach_stacktrace: true
shutdown_timeout: 1s 500ms
"#;
let expected_output = SentryConfig {
dsn: SecureString::from("some_dsn"),
debug: true,
sample_rate: 0.5,
traces_sample_rate: 0.4,
max_breadcrumbs: 50,
attach_stacktrace: true,
shutdown_timeout: Duration::from_millis(1500),
};
let actual_output = serde_yml::from_str::<SentryConfig>(input).unwrap();
assert_eq!(expected_output, actual_output);
}
}