use super::{TelemetryConfig, TelemetryEvent};
use std::io;
use std::time::Duration;
pub struct TelemetryClient {
endpoint: String,
installation_id: String,
client: reqwest::blocking::Client,
}
impl TelemetryClient {
pub fn new(config: &TelemetryConfig) -> io::Result<Self> {
let installation_id = config.installation_id.clone().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "No installation ID configured")
})?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(5))
.connect_timeout(Duration::from_secs(3))
.user_agent(format!("agnix/{}", env!("CARGO_PKG_VERSION")))
.build()
.map_err(|e| {
io::Error::other(format!("Failed to create HTTP client: {}", e))
})?;
Ok(Self {
endpoint: config.endpoint().to_string(),
installation_id,
client,
})
}
pub fn submit_batch(&self, events: &[TelemetryEvent]) -> io::Result<()> {
if events.is_empty() {
return Ok(());
}
for event in events {
event
.validate_privacy()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
}
let payload = BatchPayload {
installation_id: &self.installation_id,
events,
};
let response = self
.client
.post(&self.endpoint)
.json(&payload)
.send()
.map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("Failed to submit telemetry: {}", e),
)
})?;
if response.status().is_success() {
Ok(())
} else {
Err(io::Error::other(format!(
"Telemetry server returned error: {}",
response.status()
)))
}
}
}
#[derive(serde::Serialize)]
struct BatchPayload<'a> {
installation_id: &'a str,
events: &'a [TelemetryEvent],
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_client_requires_installation_id() {
let config = TelemetryConfig::default();
let result = TelemetryClient::new(&config);
assert!(result.is_err());
}
#[test]
fn test_batch_payload_serialization() {
let events = vec![TelemetryEvent::ValidationRun(
super::super::ValidationRunEvent {
file_type_counts: HashMap::new(),
rule_trigger_counts: HashMap::new(),
error_count: 0,
warning_count: 0,
info_count: 0,
duration_ms: 100,
timestamp: "2024-01-01T00:00:00Z".to_string(),
},
)];
let payload = BatchPayload {
installation_id: "test-id",
events: &events,
};
let json = serde_json::to_string(&payload)
.expect("serialization of TelemetryPayload should not fail");
assert!(json.contains("\"installation_id\":\"test-id\""));
assert!(json.contains("\"events\":["));
}
}