use crate::contract_drift_types::{ConsumerImpact, FitnessTestResult};
use crate::protocol::Protocol;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum IncidentStatus {
Open,
Acknowledged,
Resolved,
Closed,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum IncidentType {
BreakingChange,
ThresholdExceeded,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "lowercase")]
pub enum IncidentSeverity {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DriftIncident {
pub id: String,
pub budget_id: Option<String>,
pub workspace_id: Option<String>,
pub endpoint: String,
pub method: String,
pub incident_type: IncidentType,
pub severity: IncidentSeverity,
pub status: IncidentStatus,
pub detected_at: i64,
pub resolved_at: Option<i64>,
pub details: serde_json::Value,
pub external_ticket_id: Option<String>,
pub external_ticket_url: Option<String>,
pub created_at: i64,
pub updated_at: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sync_cycle_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contract_diff_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub before_sample: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub after_sample: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fitness_test_results: Vec<FitnessTestResult>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub affected_consumers: Option<ConsumerImpact>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub protocol: Option<Protocol>,
}
impl DriftIncident {
pub fn new(
id: String,
endpoint: String,
method: String,
incident_type: IncidentType,
severity: IncidentSeverity,
details: serde_json::Value,
) -> Self {
let now = chrono::Utc::now().timestamp();
Self {
id,
budget_id: None,
workspace_id: None,
endpoint,
method,
incident_type,
severity,
status: IncidentStatus::Open,
detected_at: now,
resolved_at: None,
details,
external_ticket_id: None,
external_ticket_url: None,
created_at: now,
updated_at: now,
sync_cycle_id: None,
contract_diff_id: None,
before_sample: None,
after_sample: None,
fitness_test_results: Vec::new(),
affected_consumers: None,
protocol: None,
}
}
pub fn acknowledge(&mut self) {
if self.status == IncidentStatus::Open {
self.status = IncidentStatus::Acknowledged;
self.updated_at = chrono::Utc::now().timestamp();
}
}
pub fn resolve(&mut self) {
if self.status != IncidentStatus::Closed {
self.status = IncidentStatus::Resolved;
self.resolved_at = Some(chrono::Utc::now().timestamp());
self.updated_at = chrono::Utc::now().timestamp();
}
}
pub fn close(&mut self) {
self.status = IncidentStatus::Closed;
if self.resolved_at.is_none() {
self.resolved_at = Some(chrono::Utc::now().timestamp());
}
self.updated_at = chrono::Utc::now().timestamp();
}
pub fn link_external_ticket(&mut self, ticket_id: String, ticket_url: Option<String>) {
self.external_ticket_id = Some(ticket_id);
self.external_ticket_url = ticket_url;
self.updated_at = chrono::Utc::now().timestamp();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalTicket {
pub ticket_id: String,
pub ticket_url: Option<String>,
pub system_type: String,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default)]
pub struct IncidentQuery {
pub status: Option<IncidentStatus>,
pub severity: Option<IncidentSeverity>,
pub endpoint: Option<String>,
pub method: Option<String>,
pub incident_type: Option<IncidentType>,
pub workspace_id: Option<String>,
pub start_date: Option<i64>,
pub end_date: Option<i64>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}