telemetry_kit/
event.rs

1//! Event schema types and builders
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use uuid::Uuid;
7
8/// Schema version for events
9pub const SCHEMA_VERSION: &str = "1.0.0";
10
11/// Complete event structure as sent to the server
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Event {
14    /// Schema version
15    pub schema_version: String,
16
17    /// Unique event identifier
18    pub event_id: Uuid,
19
20    /// When the event occurred
21    pub timestamp: DateTime<Utc>,
22
23    /// Service information
24    pub service: ServiceInfo,
25
26    /// User identification (anonymous)
27    pub user_id: String,
28
29    /// Session identifier
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub session_id: Option<String>,
32
33    /// Environment information
34    pub environment: Environment,
35
36    /// The actual event data
37    pub event: EventData,
38
39    /// Metadata about transmission
40    pub metadata: Metadata,
41}
42
43/// Service/application information
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ServiceInfo {
46    /// Service name (e.g., "my-cli")
47    pub name: String,
48
49    /// Service version (e.g., "1.2.0")
50    pub version: String,
51
52    /// Programming language
53    pub language: String,
54
55    /// Language version
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub language_version: Option<String>,
58}
59
60/// Environment/system information
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Environment {
63    /// Operating system (linux, macos, windows, etc.)
64    pub os: String,
65
66    /// OS version
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub os_version: Option<String>,
69
70    /// System architecture (x86_64, aarch64, etc.)
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub arch: Option<String>,
73
74    /// Whether running in CI environment
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub ci: Option<bool>,
77
78    /// Shell type (bash, zsh, etc.)
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub shell: Option<String>,
81}
82
83/// Event data container
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct EventData {
86    /// Event type (command_execution, feature_used, etc.)
87    #[serde(rename = "type")]
88    pub event_type: String,
89
90    /// Event category (usage, error, etc.)
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub category: Option<String>,
93
94    /// Event-specific data
95    pub data: serde_json::Value,
96}
97
98/// Transmission metadata
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct Metadata {
101    /// SDK version
102    pub sdk_version: String,
103
104    /// When the event was transmitted
105    pub transmission_timestamp: DateTime<Utc>,
106
107    /// Number of events in this batch
108    pub batch_size: usize,
109
110    /// Number of retry attempts
111    pub retry_count: u32,
112}
113
114/// Builder for command execution events
115#[derive(Debug, Default)]
116pub struct CommandEventBuilder {
117    command: String,
118    subcommand: Option<String>,
119    flags: Vec<String>,
120    success: Option<bool>,
121    duration_ms: Option<u64>,
122    exit_code: Option<i32>,
123}
124
125impl CommandEventBuilder {
126    /// Create a new command event builder
127    pub fn new(command: impl Into<String>) -> Self {
128        Self {
129            command: command.into(),
130            ..Default::default()
131        }
132    }
133
134    /// Set subcommand
135    pub fn subcommand(mut self, subcommand: impl Into<String>) -> Self {
136        self.subcommand = Some(subcommand.into());
137        self
138    }
139
140    /// Add a flag
141    pub fn flag(mut self, flag: impl Into<String>) -> Self {
142        self.flags.push(flag.into());
143        self
144    }
145
146    /// Set success status
147    pub fn success(mut self, success: bool) -> Self {
148        self.success = Some(success);
149        self
150    }
151
152    /// Set duration in milliseconds
153    pub fn duration_ms(mut self, duration_ms: u64) -> Self {
154        self.duration_ms = Some(duration_ms);
155        self
156    }
157
158    /// Set exit code
159    pub fn exit_code(mut self, exit_code: i32) -> Self {
160        self.exit_code = Some(exit_code);
161        self
162    }
163
164    /// Build the event data
165    pub fn build(self) -> serde_json::Value {
166        let mut data = HashMap::new();
167        data.insert("command".to_string(), serde_json::json!(self.command));
168
169        if let Some(subcommand) = self.subcommand {
170            data.insert("subcommand".to_string(), serde_json::json!(subcommand));
171        }
172
173        if !self.flags.is_empty() {
174            data.insert("flags".to_string(), serde_json::json!(self.flags));
175        }
176
177        if let Some(success) = self.success {
178            data.insert("success".to_string(), serde_json::json!(success));
179        }
180
181        if let Some(duration_ms) = self.duration_ms {
182            data.insert("duration_ms".to_string(), serde_json::json!(duration_ms));
183        }
184
185        if let Some(exit_code) = self.exit_code {
186            data.insert("exit_code".to_string(), serde_json::json!(exit_code));
187        }
188
189        serde_json::json!(data)
190    }
191}
192
193/// Builder for feature usage events
194#[derive(Debug, Default)]
195pub struct FeatureEventBuilder {
196    feature: String,
197    method: Option<String>,
198    success: Option<bool>,
199    custom_data: HashMap<String, serde_json::Value>,
200}
201
202impl FeatureEventBuilder {
203    /// Create a new feature event builder
204    pub fn new(feature: impl Into<String>) -> Self {
205        Self {
206            feature: feature.into(),
207            ..Default::default()
208        }
209    }
210
211    /// Set method/variant
212    pub fn method(mut self, method: impl Into<String>) -> Self {
213        self.method = Some(method.into());
214        self
215    }
216
217    /// Set success status
218    pub fn success(mut self, success: bool) -> Self {
219        self.success = Some(success);
220        self
221    }
222
223    /// Add custom data field
224    pub fn data(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
225        self.custom_data.insert(key.into(), value);
226        self
227    }
228
229    /// Build the event data
230    pub fn build(self) -> serde_json::Value {
231        let mut data = HashMap::new();
232        data.insert("feature".to_string(), serde_json::json!(self.feature));
233
234        if let Some(method) = self.method {
235            data.insert("method".to_string(), serde_json::json!(method));
236        }
237
238        if let Some(success) = self.success {
239            data.insert("success".to_string(), serde_json::json!(success));
240        }
241
242        for (key, value) in self.custom_data {
243            data.insert(key, value);
244        }
245
246        serde_json::json!(data)
247    }
248}
249
250/// Batch of events for transmission
251#[derive(Debug, Serialize, Deserialize)]
252pub struct EventBatch {
253    /// Events in this batch
254    pub events: Vec<Event>,
255}
256
257impl EventBatch {
258    /// Create a new event batch
259    pub fn new(events: Vec<Event>) -> Self {
260        Self { events }
261    }
262
263    /// Get the batch size
264    pub fn size(&self) -> usize {
265        self.events.len()
266    }
267
268    /// Check if batch is empty
269    pub fn is_empty(&self) -> bool {
270        self.events.is_empty()
271    }
272}