use super::common::add_troubleshooting_info;
use crate::events::relay::{ConnectionIssueType, DeliveryStatus, RelayConfigType, RelayEvent};
use serde_json::{json, Map, Value};
pub struct RelayJsonFormatter;
impl RelayJsonFormatter {
pub fn new() -> Self {
Self
}
pub fn format_event(&self, event: &RelayEvent) -> Value {
let mut base_object = Map::new();
base_object.insert("component".to_string(), json!("relay"));
match event {
RelayEvent::DeliveryStatus {
base: _base,
queue_id,
recipient,
relay_host,
relay_ip,
relay_port,
delay,
delays,
dsn,
status,
status_description,
} => {
base_object.insert("event_type".to_string(), json!("delivery_status"));
base_object.insert("queue_id".to_string(), json!(queue_id));
base_object.insert("recipient".to_string(), json!(recipient));
base_object.insert("relay_host".to_string(), json!(relay_host));
base_object.insert("base".to_string(), json!(_base));
if let Some(ip) = relay_ip {
base_object.insert("relay_ip".to_string(), json!(ip.to_string()));
}
if let Some(port) = relay_port {
base_object.insert("relay_port".to_string(), json!(port));
}
base_object.insert("delay_total_seconds".to_string(), json!(delay));
let delay_details = json!({
"queue_wait_seconds": delays.queue_wait,
"connection_setup_seconds": delays.connection_setup,
"connection_time_seconds": delays.connection_time,
"transmission_time_seconds": delays.transmission_time,
"total_seconds": delays.total_delay(),
"performance_analysis": self.analyze_delays(delays)
});
base_object.insert("delay_breakdown".to_string(), delay_details);
base_object.insert("dsn_code".to_string(), json!(dsn));
base_object.insert(
"dsn_classification".to_string(),
json!(self.classify_dsn(dsn)),
);
base_object.insert("delivery_status".to_string(), json!(status));
base_object.insert("status_description".to_string(), json!(status_description));
base_object.insert(
"delivery_analysis".to_string(),
self.analyze_delivery_status(status, dsn, status_description),
);
base_object.insert(
"relay_health".to_string(),
self.assess_relay_health(relay_host, delay, status),
);
if matches!(
status,
DeliveryStatus::Deferred | DeliveryStatus::Failed | DeliveryStatus::Bounced
) {
add_troubleshooting_info(
&mut base_object,
&format!("relay delivery {} for {}", status, relay_host),
);
}
}
RelayEvent::ConnectionIssue {
base: _base,
queue_id,
recipient,
relay_host,
relay_ip,
issue_type,
error_message,
} => {
base_object.insert("event_type".to_string(), json!("connection_issue"));
base_object.insert("queue_id".to_string(), json!(queue_id));
base_object.insert("recipient".to_string(), json!(recipient));
base_object.insert("relay_host".to_string(), json!(relay_host));
base_object.insert("base".to_string(), json!(_base));
if let Some(ip) = relay_ip {
base_object.insert("relay_ip".to_string(), json!(ip.to_string()));
}
base_object.insert("issue_type".to_string(), json!(issue_type));
base_object.insert("error_message".to_string(), json!(error_message));
base_object.insert(
"connection_analysis".to_string(),
self.analyze_connection_issue(issue_type, relay_host),
);
add_troubleshooting_info(
&mut base_object,
&format!("relay connection {} to {}", issue_type, relay_host),
);
}
RelayEvent::RelayConfiguration {
base: _base,
config_type,
details,
} => {
base_object.insert("event_type".to_string(), json!("relay_configuration"));
base_object.insert("config_type".to_string(), json!(config_type));
base_object.insert("config_details".to_string(), json!(details));
base_object.insert("base".to_string(), json!(_base));
base_object.insert(
"config_analysis".to_string(),
self.analyze_config_event(config_type, details),
);
}
}
base_object.insert(
"relay_statistics".to_string(),
self.get_relay_statistics(event),
);
Value::Object(base_object)
}
fn analyze_delays(&self, delays: &crate::events::relay::DelayBreakdown) -> Value {
let total = delays.total_delay();
let mut analysis = Map::new();
let performance_rating = if total < 1.0 {
"excellent"
} else if total < 5.0 {
"good"
} else if total < 30.0 {
"fair"
} else {
"poor"
};
analysis.insert("overall_rating".to_string(), json!(performance_rating));
let bottleneck = if delays.connection_time > total * 0.5 {
"connection_establishment"
} else if delays.transmission_time > total * 0.5 {
"data_transmission"
} else if delays.queue_wait > total * 0.5 {
"queue_processing"
} else {
"balanced"
};
analysis.insert("primary_bottleneck".to_string(), json!(bottleneck));
if total > 0.0 {
analysis.insert(
"phase_percentages".to_string(),
json!({
"queue_wait": (delays.queue_wait / total * 100.0).round(),
"connection_setup": (delays.connection_setup / total * 100.0).round(),
"connection_time": (delays.connection_time / total * 100.0).round(),
"transmission_time": (delays.transmission_time / total * 100.0).round()
}),
);
}
Value::Object(analysis)
}
fn analyze_delivery_status(
&self,
status: &DeliveryStatus,
dsn: &str,
_description: &str,
) -> Value {
let mut analysis = Map::new();
let result_category = match status {
DeliveryStatus::Sent => "successful",
DeliveryStatus::Deferred => "temporary_failure",
DeliveryStatus::Bounced => "permanent_failure",
DeliveryStatus::Failed => "permanent_failure",
DeliveryStatus::Rejected => "permanent_failure",
};
analysis.insert("result_category".to_string(), json!(result_category));
analysis.insert(
"is_retryable".to_string(),
json!(matches!(status, DeliveryStatus::Deferred)),
);
let dsn_class = self.classify_dsn(dsn);
analysis.insert("dsn_class".to_string(), json!(dsn_class));
let recommended_action = match status {
DeliveryStatus::Sent => "none",
DeliveryStatus::Deferred => "monitor_retry",
DeliveryStatus::Bounced => "check_recipient_address",
DeliveryStatus::Failed => "investigate_server_config",
DeliveryStatus::Rejected => "verify_sender_reputation",
};
analysis.insert("recommended_action".to_string(), json!(recommended_action));
Value::Object(analysis)
}
fn assess_relay_health(&self, relay_host: &str, delay: &f64, status: &DeliveryStatus) -> Value {
let mut health = Map::new();
let performance_score = if *delay < 1.0 {
100
} else if *delay < 5.0 {
80
} else if *delay < 30.0 {
60
} else {
30
};
let reliability_score = match status {
DeliveryStatus::Sent => 100,
DeliveryStatus::Deferred => 70,
DeliveryStatus::Bounced => 30,
DeliveryStatus::Failed => 20,
DeliveryStatus::Rejected => 20,
};
let overall_score = (performance_score + reliability_score) / 2;
let health_status = if overall_score >= 90 {
"excellent"
} else if overall_score >= 70 {
"good"
} else if overall_score >= 50 {
"warning"
} else {
"critical"
};
health.insert("relay_host".to_string(), json!(relay_host));
health.insert("performance_score".to_string(), json!(performance_score));
health.insert("reliability_score".to_string(), json!(reliability_score));
health.insert("overall_score".to_string(), json!(overall_score));
health.insert("health_status".to_string(), json!(health_status));
Value::Object(health)
}
fn analyze_connection_issue(
&self,
issue_type: &ConnectionIssueType,
relay_host: &str,
) -> Value {
let mut analysis = Map::new();
let (severity, likely_cause, resolution_time) = match issue_type {
ConnectionIssueType::LostConnection => {
("medium", "network_instability", "5-30 minutes")
}
ConnectionIssueType::ConnectionTimeout => {
("medium", "network_congestion", "5-60 minutes")
}
ConnectionIssueType::ConnectionRefused => {
("high", "service_unavailable", "immediate to hours")
}
ConnectionIssueType::DnsResolutionFailed => {
("high", "dns_configuration", "immediate to hours")
}
ConnectionIssueType::TlsHandshakeFailed => {
("high", "certificate_issue", "immediate to days")
}
ConnectionIssueType::AuthenticationFailed => ("high", "credential_issue", "immediate"),
ConnectionIssueType::Other => ("medium", "unknown", "varies"),
};
analysis.insert("severity".to_string(), json!(severity));
analysis.insert("likely_cause".to_string(), json!(likely_cause));
analysis.insert(
"expected_resolution_time".to_string(),
json!(resolution_time),
);
analysis.insert("affected_relay".to_string(), json!(relay_host));
Value::Object(analysis)
}
fn analyze_config_event(&self, config_type: &RelayConfigType, details: &str) -> Value {
let mut analysis = Map::new();
let importance = match config_type {
RelayConfigType::RelayHostConfig => "critical",
RelayConfigType::TransportMapping => "high",
RelayConfigType::AuthConfig => "high",
RelayConfigType::TlsConfig => "medium",
};
analysis.insert("importance".to_string(), json!(importance));
analysis.insert("config_type".to_string(), json!(config_type));
analysis.insert(
"requires_attention".to_string(),
json!(details.contains("error") || details.contains("warning")),
);
Value::Object(analysis)
}
fn classify_dsn(&self, dsn: &str) -> &'static str {
if dsn.starts_with("2.") {
"success"
} else if dsn.starts_with("4.") {
"temporary_failure"
} else if dsn.starts_with("5.") {
"permanent_failure"
} else {
"unknown"
}
}
fn get_relay_statistics(&self, event: &RelayEvent) -> Value {
let mut stats = Map::new();
stats.insert("component".to_string(), json!("relay"));
stats.insert(
"component_description".to_string(),
json!("Postfix中继传输代理"),
);
match event {
RelayEvent::DeliveryStatus {
relay_host, status, ..
} => {
stats.insert("relay_destination".to_string(), json!(relay_host));
stats.insert("operation_type".to_string(), json!("mail_delivery"));
stats.insert("operation_result".to_string(), json!(status));
}
RelayEvent::ConnectionIssue {
relay_host,
issue_type,
..
} => {
stats.insert("relay_destination".to_string(), json!(relay_host));
stats.insert("operation_type".to_string(), json!("connection"));
stats.insert("operation_result".to_string(), json!(issue_type));
}
RelayEvent::RelayConfiguration { config_type, .. } => {
stats.insert("operation_type".to_string(), json!("configuration"));
stats.insert("config_category".to_string(), json!(config_type));
}
}
Value::Object(stats)
}
}
impl Default for RelayJsonFormatter {
fn default() -> Self {
Self::new()
}
}