use axum::{extract::State, response::Json};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};
pub trait IncidentReplayProvider: Clone + Send + Sync + 'static {
type Timeline: DeserializeOwned + Serialize + Send + Sync + 'static;
type Scenario: Serialize;
fn generate_scenario(&self, timeline: &Self::Timeline) -> Self::Scenario;
fn export_scenario_to_json(&self, scenario: &Self::Scenario) -> Result<String, String>;
fn export_scenario_to_yaml(&self, scenario: &Self::Scenario) -> Result<String, String>;
fn import_pagerduty(&self, data: &Value) -> Result<Self::Timeline, String>;
fn import_datadog(&self, data: &Value) -> Result<Self::Timeline, String>;
}
#[derive(Debug, Deserialize)]
pub struct GenerateReplayRequest<T> {
pub timeline: T,
#[serde(default)]
pub format: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct ImportIncidentRequest {
pub data: Value,
pub format: String,
}
#[derive(Debug, Serialize)]
pub struct ReplayGenerationResponse<C> {
pub success: bool,
pub scenario: C,
pub scenario_json: String,
pub scenario_yaml: String,
}
pub async fn generate_replay<S: IncidentReplayProvider>(
State(state): State<S>,
Json(request): Json<GenerateReplayRequest<S::Timeline>>,
) -> Result<Json<Value>, String> {
let generator = &state;
let scenario = generator.generate_scenario(&request.timeline);
let scenario_json = generator
.export_scenario_to_json(&scenario)
.map_err(|e| format!("Failed to export scenario to JSON: {}", e))?;
let scenario_yaml = generator
.export_scenario_to_yaml(&scenario)
.map_err(|e| format!("Failed to export scenario to YAML: {}", e))?;
Ok(Json(json!({
"success": true,
"scenario": scenario,
"scenario_json": scenario_json,
"scenario_yaml": scenario_yaml,
})))
}
pub async fn import_incident<S: IncidentReplayProvider>(
State(state): State<S>,
Json(request): Json<ImportIncidentRequest>,
) -> Result<Json<Value>, String> {
let timeline = match request.format.as_str() {
"pagerduty" => state
.import_pagerduty(&request.data)
.map_err(|e| format!("Failed to parse PagerDuty format: {}", e))?,
"datadog" => state
.import_datadog(&request.data)
.map_err(|e| format!("Failed to parse Datadog format: {}", e))?,
"custom" => {
serde_json::from_value::<S::Timeline>(request.data)
.map_err(|e| format!("Failed to parse custom format: {}", e))?
}
_ => return Err(format!("Unsupported format: {}", request.format)),
};
Ok(Json(json!({
"success": true,
"timeline": timeline,
})))
}
pub async fn import_and_generate<S: IncidentReplayProvider>(
State(state): State<S>,
Json(request): Json<ImportIncidentRequest>,
) -> Result<Json<Value>, String> {
let timeline = match request.format.as_str() {
"pagerduty" => state
.import_pagerduty(&request.data)
.map_err(|e| format!("Failed to parse PagerDuty format: {}", e))?,
"datadog" => state
.import_datadog(&request.data)
.map_err(|e| format!("Failed to parse Datadog format: {}", e))?,
"custom" => serde_json::from_value::<S::Timeline>(request.data)
.map_err(|e| format!("Failed to parse custom format: {}", e))?,
_ => return Err(format!("Unsupported format: {}", request.format)),
};
let generator = &state;
let scenario = generator.generate_scenario(&timeline);
let scenario_json = generator
.export_scenario_to_json(&scenario)
.map_err(|e| format!("Failed to export scenario to JSON: {}", e))?;
let scenario_yaml = generator
.export_scenario_to_yaml(&scenario)
.map_err(|e| format!("Failed to export scenario to YAML: {}", e))?;
Ok(Json(json!({
"success": true,
"timeline": timeline,
"scenario": scenario,
"scenario_json": scenario_json,
"scenario_yaml": scenario_yaml,
})))
}