use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::errors::AnalyzerError;
use super::types::MetricValue;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalyzerContext {
metrics: HashMap<String, MetricValue>,
metadata: AnalysisMetadata,
errors: Vec<AnalysisError>,
}
impl AnalyzerContext {
pub fn new() -> Self {
Self {
metrics: HashMap::new(),
metadata: AnalysisMetadata::new(),
errors: Vec::new(),
}
}
pub fn with_dataset(dataset_name: impl Into<String>) -> Self {
Self {
metrics: HashMap::new(),
metadata: AnalysisMetadata::with_dataset(dataset_name),
errors: Vec::new(),
}
}
pub fn store_metric(&mut self, key: impl Into<String>, value: MetricValue) {
self.metrics.insert(key.into(), value);
}
pub fn store_analyzer_metric(
&mut self,
analyzer_name: &str,
metric_name: &str,
value: MetricValue,
) {
let key = format!("{analyzer_name}.{metric_name}");
self.store_metric(key, value);
}
pub fn get_metric(&self, key: &str) -> Option<&MetricValue> {
self.metrics.get(key)
}
pub fn get_analyzer_metrics(&self, analyzer_name: &str) -> HashMap<String, &MetricValue> {
let prefix = format!("{analyzer_name}.");
self.metrics
.iter()
.filter(|(k, _)| k.starts_with(&prefix))
.map(|(k, v)| {
let metric_name = k.strip_prefix(&prefix).unwrap_or(k);
(metric_name.to_string(), v)
})
.collect()
}
pub fn all_metrics(&self) -> &HashMap<String, MetricValue> {
&self.metrics
}
pub fn record_error(&mut self, analyzer_name: impl Into<String>, error: AnalyzerError) {
self.errors.push(AnalysisError {
analyzer_name: analyzer_name.into(),
error: error.to_string(),
});
}
pub fn errors(&self) -> &[AnalysisError] {
&self.errors
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn metadata(&self) -> &AnalysisMetadata {
&self.metadata
}
pub fn metadata_mut(&mut self) -> &mut AnalysisMetadata {
&mut self.metadata
}
pub fn merge(&mut self, other: AnalyzerContext) {
self.metrics.extend(other.metrics);
self.errors.extend(other.errors);
self.metadata.merge(other.metadata);
}
pub fn summary(&self) -> AnalysisSummary {
AnalysisSummary {
total_metrics: self.metrics.len(),
total_errors: self.errors.len(),
analyzer_count: self.count_analyzers(),
dataset_name: self.metadata.dataset_name.clone(),
}
}
fn count_analyzers(&self) -> usize {
let mut analyzers = std::collections::HashSet::new();
for key in self.metrics.keys() {
if let Some(analyzer_name) = key.split('.').next() {
analyzers.insert(analyzer_name);
}
}
analyzers.len()
}
}
impl Default for AnalyzerContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisMetadata {
pub dataset_name: Option<String>,
pub start_time: Option<chrono::DateTime<chrono::Utc>>,
pub end_time: Option<chrono::DateTime<chrono::Utc>>,
pub custom: HashMap<String, String>,
}
impl AnalysisMetadata {
pub fn new() -> Self {
Self {
dataset_name: None,
start_time: None,
end_time: None,
custom: HashMap::new(),
}
}
pub fn with_dataset(name: impl Into<String>) -> Self {
Self {
dataset_name: Some(name.into()),
start_time: None,
end_time: None,
custom: HashMap::new(),
}
}
pub fn record_start(&mut self) {
self.start_time = Some(chrono::Utc::now());
}
pub fn record_end(&mut self) {
self.end_time = Some(chrono::Utc::now());
}
pub fn duration(&self) -> Option<chrono::Duration> {
match (self.start_time, self.end_time) {
(Some(start), Some(end)) => Some(end - start),
_ => None,
}
}
pub fn add_custom(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.custom.insert(key.into(), value.into());
}
fn merge(&mut self, other: AnalysisMetadata) {
if self.dataset_name.is_none() {
self.dataset_name = other.dataset_name;
}
if self.start_time.is_none() {
self.start_time = other.start_time;
}
if self.end_time.is_none() {
self.end_time = other.end_time;
}
self.custom.extend(other.custom);
}
}
impl Default for AnalysisMetadata {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisError {
pub analyzer_name: String,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisSummary {
pub total_metrics: usize,
pub total_errors: usize,
pub analyzer_count: usize,
pub dataset_name: Option<String>,
}