mockforge_http/handlers/
incident_replay.rs

1//! Incident Replay API handlers
2
3use axum::extract::{Path, Query, State};
4use axum::response::Json;
5use mockforge_chaos::incident_replay::{
6    IncidentFormatAdapter, IncidentReplayGenerator, IncidentTimeline,
7};
8use mockforge_chaos::OrchestratedScenario;
9use serde::{Deserialize, Serialize};
10use serde_json::{json, Value};
11use std::sync::Arc;
12
13/// Incident replay API state
14#[derive(Clone)]
15pub struct IncidentReplayState {
16    /// Incident replay generator
17    pub generator: Arc<IncidentReplayGenerator>,
18}
19
20impl IncidentReplayState {
21    /// Create new incident replay state
22    pub fn new() -> Self {
23        Self {
24            generator: Arc::new(IncidentReplayGenerator::new()),
25        }
26    }
27}
28
29impl Default for IncidentReplayState {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35/// Request to generate a chaos scenario from an incident timeline
36#[derive(Debug, Deserialize)]
37pub struct GenerateReplayRequest {
38    /// Incident timeline data
39    pub timeline: IncidentTimeline,
40    /// Format of the timeline (if importing from external source)
41    #[serde(default)]
42    pub format: Option<String>,
43}
44
45/// Request to import an incident timeline from external format
46#[derive(Debug, Deserialize)]
47pub struct ImportIncidentRequest {
48    /// Incident data in external format (JSON)
49    pub data: Value,
50    /// Format type: "pagerduty", "datadog", "custom"
51    pub format: String,
52}
53
54/// Response for replay generation
55#[derive(Debug, Serialize)]
56pub struct ReplayGenerationResponse {
57    /// Success flag
58    pub success: bool,
59    /// Generated orchestrated scenario
60    pub scenario: OrchestratedScenario,
61    /// Scenario in JSON format
62    pub scenario_json: String,
63    /// Scenario in YAML format
64    pub scenario_yaml: String,
65}
66
67/// Generate a chaos scenario from an incident timeline
68///
69/// POST /api/v1/chaos/incident-replay/generate
70pub async fn generate_replay(
71    State(state): State<IncidentReplayState>,
72    Json(request): Json<GenerateReplayRequest>,
73) -> Result<Json<Value>, String> {
74    let generator = &state.generator;
75    let scenario = generator.generate_scenario(&request.timeline);
76
77    // Export to JSON and YAML
78    let scenario_json = generator
79        .export_scenario_to_json(&scenario)
80        .map_err(|e| format!("Failed to export scenario to JSON: {}", e))?;
81    let scenario_yaml = generator
82        .export_scenario_to_yaml(&scenario)
83        .map_err(|e| format!("Failed to export scenario to YAML: {}", e))?;
84
85    Ok(Json(json!({
86        "success": true,
87        "scenario": scenario,
88        "scenario_json": scenario_json,
89        "scenario_yaml": scenario_yaml,
90    })))
91}
92
93/// Import incident timeline from external format
94///
95/// POST /api/v1/chaos/incident-replay/import
96pub async fn import_incident(
97    State(_state): State<IncidentReplayState>,
98    Json(request): Json<ImportIncidentRequest>,
99) -> Result<Json<Value>, String> {
100    let timeline = match request.format.as_str() {
101        "pagerduty" => IncidentFormatAdapter::from_pagerduty(&request.data)
102            .map_err(|e| format!("Failed to parse PagerDuty format: {}", e))?,
103        "datadog" => IncidentFormatAdapter::from_datadog(&request.data)
104            .map_err(|e| format!("Failed to parse Datadog format: {}", e))?,
105        "custom" => {
106            // Try to parse as JSON directly
107            serde_json::from_value::<IncidentTimeline>(request.data)
108                .map_err(|e| format!("Failed to parse custom format: {}", e))?
109        }
110        _ => return Err(format!("Unsupported format: {}", request.format)),
111    };
112
113    Ok(Json(json!({
114        "success": true,
115        "timeline": timeline,
116    })))
117}
118
119/// Import and generate scenario in one step
120///
121/// POST /api/v1/chaos/incident-replay/import-and-generate
122pub async fn import_and_generate(
123    State(state): State<IncidentReplayState>,
124    Json(request): Json<ImportIncidentRequest>,
125) -> Result<Json<Value>, String> {
126    // First import the timeline
127    let timeline = match request.format.as_str() {
128        "pagerduty" => IncidentFormatAdapter::from_pagerduty(&request.data)
129            .map_err(|e| format!("Failed to parse PagerDuty format: {}", e))?,
130        "datadog" => IncidentFormatAdapter::from_datadog(&request.data)
131            .map_err(|e| format!("Failed to parse Datadog format: {}", e))?,
132        "custom" => serde_json::from_value::<IncidentTimeline>(request.data)
133            .map_err(|e| format!("Failed to parse custom format: {}", e))?,
134        _ => return Err(format!("Unsupported format: {}", request.format)),
135    };
136
137    // Then generate the scenario
138    let generator = &state.generator;
139    let scenario = generator.generate_scenario(&timeline);
140
141    // Export to JSON and YAML
142    let scenario_json = generator
143        .export_scenario_to_json(&scenario)
144        .map_err(|e| format!("Failed to export scenario to JSON: {}", e))?;
145    let scenario_yaml = generator
146        .export_scenario_to_yaml(&scenario)
147        .map_err(|e| format!("Failed to export scenario to YAML: {}", e))?;
148
149    Ok(Json(json!({
150        "success": true,
151        "timeline": timeline,
152        "scenario": scenario,
153        "scenario_json": scenario_json,
154        "scenario_yaml": scenario_yaml,
155    })))
156}