use serde::{Deserialize, Serialize, Serializer};
use std::time::Instant;
fn round_to_1_decimal(value: f64) -> f64 {
(value * 10.0).round() / 10.0
}
fn serialize_elapsed_ms<S>(elapsed_ms: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_f64(round_to_1_decimal(*elapsed_ms))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubAnalysisResult {
pub name: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(serialize_with = "serialize_elapsed_ms")]
pub elapsed_ms: f64,
}
pub fn safe_call<F, T>(name: &str, f: F) -> SubAnalysisResult
where
F: FnOnce() -> Result<T, anyhow::Error>,
T: Serialize,
{
let start = Instant::now();
match f() {
Ok(data) => {
let elapsed = start.elapsed();
SubAnalysisResult {
name: name.to_string(),
success: true,
data: Some(serde_json::to_value(&data).unwrap_or(serde_json::Value::Null)),
error: None,
elapsed_ms: elapsed.as_secs_f64() * 1000.0,
}
}
Err(e) => {
let elapsed = start.elapsed();
SubAnalysisResult {
name: name.to_string(),
success: false,
data: None,
error: Some(e.to_string()),
elapsed_ms: elapsed.as_secs_f64() * 1000.0,
}
}
}
}
pub fn progress(step: usize, total: usize, name: &str) {
eprintln!("[{}/{}] Analyzing {}...", step, total, name);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_to_1_decimal() {
assert_eq!(round_to_1_decimal(1.234), 1.2);
assert_eq!(round_to_1_decimal(1.25), 1.3); assert_eq!(round_to_1_decimal(1.0), 1.0);
assert_eq!(round_to_1_decimal(0.05), 0.1); assert_eq!(round_to_1_decimal(99.999), 100.0);
}
#[test]
fn test_sub_analysis_result_default_fields() {
let result = SubAnalysisResult {
name: "test".to_string(),
success: true,
data: None,
error: None,
elapsed_ms: 0.0,
};
assert_eq!(result.name, "test");
assert!(result.success);
}
}