statsig_rust/sdk_diagnostics/
marker.rs

1use chrono::Utc;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5use crate::evaluation::evaluation_details::EvaluationDetails;
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
8pub enum KeyType {
9    #[serde(rename = "initialize")]
10    Initialize,
11    #[serde(rename = "overall")]
12    Overall,
13    #[serde(rename = "download_config_specs")]
14    DownloadConfigSpecs,
15    #[serde(rename = "get_id_list")]
16    GetIDList,
17    #[serde(rename = "get_id_list_sources")]
18    GetIDListSources,
19}
20
21#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
22pub enum StepType {
23    #[serde(rename = "process")]
24    Process,
25    #[serde(rename = "network_request")]
26    NetworkRequest,
27}
28
29#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
30pub enum ActionType {
31    #[serde(rename = "start")]
32    Start,
33    #[serde(rename = "end")]
34    End,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38#[serde(rename_all = "camelCase")]
39pub struct Marker {
40    pub key: KeyType,
41
42    action: ActionType,
43
44    timestamp: u64,
45
46    #[serde(skip_serializing_if = "Option::is_none")]
47    attempt: Option<u32>,
48
49    #[serde(skip_serializing_if = "Option::is_none")]
50    config_name: Option<String>,
51
52    #[serde(skip_serializing_if = "Option::is_none")]
53    error: Option<HashMap<String, String>>,
54
55    #[serde(skip_serializing_if = "Option::is_none")]
56    id_list_count: Option<u32>,
57
58    #[serde(rename = "markerID")]
59    #[serde(skip_serializing_if = "Option::is_none")]
60    marker_id: Option<String>,
61
62    #[serde(skip_serializing_if = "Option::is_none")]
63    message: Option<String>,
64
65    #[serde(skip_serializing_if = "Option::is_none")]
66    sdk_region: Option<String>,
67
68    #[serde(skip_serializing_if = "Option::is_none")]
69    status_code: Option<u16>,
70
71    #[serde(skip_serializing_if = "Option::is_none")]
72    step: Option<StepType>,
73
74    #[serde(skip_serializing_if = "Option::is_none")]
75    success: Option<bool>,
76
77    #[serde(skip_serializing_if = "Option::is_none")]
78    url: Option<String>,
79
80    #[serde(skip_serializing_if = "Option::is_none")]
81    evaluation_details: Option<EvaluationDetails>,
82
83    #[serde(skip_serializing_if = "Option::is_none")]
84    config_spec_ready: Option<bool>,
85
86    #[serde(skip_serializing_if = "Option::is_none")]
87    source: Option<String>,
88}
89
90impl Marker {
91    #[must_use]
92    pub fn new(key: KeyType, action: ActionType, step: Option<StepType>) -> Self {
93        Self {
94            key,
95            action,
96            step,
97            success: None,
98            timestamp: Utc::now().timestamp_millis() as u64,
99            status_code: None,
100            url: None,
101            id_list_count: None,
102            sdk_region: None,
103            marker_id: None,
104            attempt: None,
105            config_name: None,
106            message: None,
107            error: None,
108            evaluation_details: None,
109            config_spec_ready: None,
110            source: None,
111        }
112    }
113
114    #[must_use]
115    pub fn with_is_success(mut self, success: bool) -> Self {
116        self.success = Some(success);
117        self
118    }
119
120    #[must_use]
121    pub fn with_status_code(mut self, status_code: u16) -> Self {
122        self.status_code = Some(status_code);
123        self
124    }
125
126    #[must_use]
127    pub fn with_attempt(mut self, attempt: u32) -> Self {
128        self.attempt = Some(attempt);
129        self
130    }
131
132    #[must_use]
133    pub fn with_url(mut self, url: String) -> Self {
134        self.url = Some(url);
135        self
136    }
137
138    #[must_use]
139    pub fn with_message(mut self, message: String) -> Self {
140        self.message = Some(message);
141        self
142    }
143
144    #[must_use]
145    pub fn with_eval_details(mut self, details: EvaluationDetails) -> Self {
146        self.evaluation_details = Some(details);
147        self
148    }
149
150    #[must_use]
151    pub fn with_config_spec_ready(mut self, ready: bool) -> Self {
152        self.config_spec_ready = Some(ready);
153        self
154    }
155
156    #[must_use]
157    pub fn with_source(mut self, source: String) -> Self {
158        self.source = Some(source);
159        self
160    }
161
162    #[must_use]
163    pub fn with_sdk_region(mut self, region: Option<String>) -> Self {
164        self.sdk_region = region;
165        self
166    }
167
168    #[must_use]
169    pub fn with_id_list_count(mut self, count: usize) -> Self {
170        if count <= u32::MAX as usize {
171            self.id_list_count = Some(count as u32);
172        }
173        self
174    }
175
176    #[must_use]
177    pub fn with_error(mut self, error: HashMap<String, String>) -> Self {
178        self.error = Some(error);
179        self
180    }
181
182    // TODO add more as needed
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_marker_new() {
191        let timestamp: u64 = Utc::now().timestamp_millis() as u64;
192        let marker = Marker::new(
193            KeyType::Initialize,
194            ActionType::Start,
195            Some(StepType::Process),
196        );
197
198        assert_eq!(marker.key, KeyType::Initialize);
199        assert_eq!(marker.action, ActionType::Start);
200        assert_eq!(marker.step, Some(StepType::Process));
201        assert_eq!(marker.success, None);
202        assert_eq!(marker.status_code, None);
203        assert_eq!(marker.timestamp, timestamp);
204    }
205
206    #[test]
207    fn test_marker_serialization() {
208        let timestamp: u64 = Utc::now().timestamp_millis() as u64;
209        let marker = Marker::new(
210            KeyType::Initialize,
211            ActionType::Start,
212            Some(StepType::NetworkRequest),
213        )
214        .with_is_success(true)
215        .with_status_code(200)
216        .with_attempt(1);
217
218        let serialized = serde_json::to_string(&marker).expect("Failed to serialize Marker");
219        let expected_json = format!(
220            r#"{{"key":"initialize","action":"start","timestamp":{timestamp},"attempt":1,"statusCode":200,"step":"network_request","success":true}}"#
221        );
222        assert_eq!(serialized, expected_json);
223    }
224}