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
use sentry::ClientOptions;
use sentry_types::Dsn;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DrCodeError {
    #[error("Missing required configuration field: {0}")]
    MissingField(String),
    #[error("Failed to initialize Sentry: {0}")]
    InitializationError(String),
}

pub struct Config {
    pub public_key: String,
    pub project_id: String,
    pub traces_sample_rate: f32,
    pub profiles_sample_rate: f32,
}

pub struct DrCode {
    config: Config,
    dsn: Dsn,
}

impl DrCode {
    pub fn new(config: Config) -> Result<Self, DrCodeError> {
        Self::validate_config(&config)?;
        let dsn = Self::construct_dsn(&config)?;
        Ok(Self { config, dsn })
    }

    fn validate_config(config: &Config) -> Result<(), DrCodeError> {
        if config.public_key.is_empty() {
            return Err(DrCodeError::MissingField("publicKey".to_string()));
        }
        if config.project_id.is_empty() {
            return Err(DrCodeError::MissingField("projectId".to_string()));
        }
        Ok(())
    }

    fn construct_dsn(config: &Config) -> Result<Dsn, DrCodeError> {
        let dsn_string = format!(
            "https://{}@pulse.drcode.ai:443/{}",
            config.public_key, config.project_id
        );
        dsn_string.parse::<Dsn>().map_err(|e| DrCodeError::InitializationError(e.to_string()))
    }

    pub fn init(&self) -> Result<(), DrCodeError> {
        let options = ClientOptions {
            dsn: Some(self.dsn.clone()),
            traces_sample_rate: self.config.traces_sample_rate,
            // Note: profiles_sample_rate is no longer part of ClientOptions
            ..Default::default()
        };

        let _guard = sentry::init(options);
        Ok(())
    }

    pub fn capture_message(&self, message: &str, level: sentry::Level) {
        sentry::capture_message(message, level);
    }

    pub fn capture_exception(&self, error: &dyn std::error::Error) {
        sentry::capture_error(error);
    }
}

pub fn error_handler<E>(err: E)
where
    E: std::error::Error + Send + Sync + 'static,
{
    sentry::capture_error(&err);
}

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

    #[test]
    fn test_new_drcode() {
        let config = Config {
            public_key: "test_key".to_string(),
            project_id: "test_project".to_string(),
            traces_sample_rate: 1.0,
            profiles_sample_rate: 1.0,
        };
        let result = DrCode::new(config);
        assert!(result.is_ok());
    }

    #[test]
    fn test_new_drcode_missing_field() {
        let config = Config {
            public_key: "".to_string(),
            project_id: "test_project".to_string(),
            traces_sample_rate: 1.0,
            profiles_sample_rate: 1.0,
        };
        let result = DrCode::new(config);
        assert!(matches!(result, Err(DrCodeError::MissingField(_))));
    }
}