1use axum::{
6 extract::{Path, State},
7 http::StatusCode,
8 response::Json,
9};
10use mockforge_core::scenario_studio::{
11 FlowDefinition, FlowExecutionResult, FlowExecutor, FlowType, FlowVariant,
12};
13use serde::Deserialize;
14use serde_json::Value;
15use std::collections::HashMap;
16use std::sync::Arc;
17use tokio::sync::RwLock;
18use tracing::{error, info};
19
20#[derive(Clone)]
22pub struct ScenarioStudioState {
23 flows: Arc<RwLock<HashMap<String, FlowDefinition>>>,
25 variants: Arc<RwLock<HashMap<String, FlowVariant>>>,
27}
28
29impl ScenarioStudioState {
30 pub fn new() -> Self {
32 Self {
33 flows: Arc::new(RwLock::new(HashMap::new())),
34 variants: Arc::new(RwLock::new(HashMap::new())),
35 }
36 }
37}
38
39impl Default for ScenarioStudioState {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45#[derive(Debug, Deserialize)]
47pub struct CreateFlowRequest {
48 pub name: String,
50 pub description: Option<String>,
52 pub flow_type: FlowType,
54 #[serde(default)]
56 pub tags: Vec<String>,
57}
58
59#[derive(Debug, Deserialize)]
61pub struct UpdateFlowRequest {
62 pub name: Option<String>,
64 pub description: Option<String>,
66 pub flow_type: Option<FlowType>,
68 pub steps: Option<Vec<mockforge_core::scenario_studio::FlowStep>>,
70 pub connections: Option<Vec<mockforge_core::scenario_studio::FlowConnection>>,
72 pub variables: Option<HashMap<String, Value>>,
74 pub tags: Option<Vec<String>>,
76}
77
78#[derive(Debug, Deserialize)]
80pub struct ExecuteFlowRequest {
81 #[serde(default)]
83 pub variables: HashMap<String, Value>,
84}
85
86#[derive(Debug, Deserialize)]
88pub struct CreateFlowVariantRequest {
89 pub name: String,
91 pub description: Option<String>,
93 pub flow_id: String,
95}
96
97#[derive(Debug, Deserialize)]
99pub struct WorkspaceQuery {
100 #[serde(default = "default_workspace")]
102 pub workspace: String,
103}
104
105fn default_workspace() -> String {
106 "default".to_string()
107}
108
109pub async fn create_flow(
113 State(state): State<ScenarioStudioState>,
114 Json(request): Json<CreateFlowRequest>,
115) -> Result<Json<FlowDefinition>, StatusCode> {
116 let mut flow = FlowDefinition::new(request.name, request.flow_type);
117 flow.description = request.description;
118 flow.tags = request.tags;
119
120 let flow_id = flow.id.clone();
121 let mut flows = state.flows.write().await;
122 flows.insert(flow_id.clone(), flow.clone());
123
124 info!("Created flow: {}", flow_id);
125 Ok(Json(flow))
126}
127
128pub async fn list_flows(
132 State(state): State<ScenarioStudioState>,
133) -> Result<Json<Vec<FlowDefinition>>, StatusCode> {
134 let flows = state.flows.read().await;
135 let flows_list: Vec<FlowDefinition> = flows.values().cloned().collect();
136 Ok(Json(flows_list))
137}
138
139pub async fn get_flow(
143 State(state): State<ScenarioStudioState>,
144 Path(id): Path<String>,
145) -> Result<Json<FlowDefinition>, StatusCode> {
146 let flows = state.flows.read().await;
147 let flow = flows.get(&id).cloned().ok_or_else(|| {
148 error!("Flow not found: {}", id);
149 StatusCode::NOT_FOUND
150 })?;
151
152 Ok(Json(flow))
153}
154
155pub async fn update_flow(
159 State(state): State<ScenarioStudioState>,
160 Path(id): Path<String>,
161 Json(request): Json<UpdateFlowRequest>,
162) -> Result<Json<FlowDefinition>, StatusCode> {
163 let mut flows = state.flows.write().await;
164 let flow = flows.get_mut(&id).ok_or_else(|| {
165 error!("Flow not found: {}", id);
166 StatusCode::NOT_FOUND
167 })?;
168
169 if let Some(name) = request.name {
170 flow.name = name;
171 }
172 if let Some(description) = request.description {
173 flow.description = Some(description);
174 }
175 if let Some(flow_type) = request.flow_type {
176 flow.flow_type = flow_type;
177 }
178 if let Some(steps) = request.steps {
179 flow.steps = steps;
180 }
181 if let Some(connections) = request.connections {
182 flow.connections = connections;
183 }
184 if let Some(variables) = request.variables {
185 flow.variables = variables;
186 }
187 if let Some(tags) = request.tags {
188 flow.tags = tags;
189 }
190
191 flow.updated_at = chrono::Utc::now();
192
193 let flow_clone = flow.clone();
194 info!("Updated flow: {}", id);
195 Ok(Json(flow_clone))
196}
197
198pub async fn delete_flow(
202 State(state): State<ScenarioStudioState>,
203 Path(id): Path<String>,
204) -> Result<Json<Value>, StatusCode> {
205 let mut flows = state.flows.write().await;
206 if flows.remove(&id).is_none() {
207 error!("Flow not found: {}", id);
208 return Err(StatusCode::NOT_FOUND);
209 }
210
211 let mut variants = state.variants.write().await;
213 variants.retain(|_, v| v.flow_id != id);
214
215 info!("Deleted flow: {}", id);
216 Ok(Json(serde_json::json!({
217 "success": true,
218 "message": format!("Flow {} deleted", id),
219 })))
220}
221
222#[axum::debug_handler]
226pub async fn execute_flow(
227 Path(id): Path<String>,
228 State(state): State<ScenarioStudioState>,
229 Json(request): Json<ExecuteFlowRequest>,
230) -> Result<Json<FlowExecutionResult>, StatusCode> {
231 let flows = state.flows.read().await;
232 let flow = flows.get(&id).cloned().ok_or_else(|| {
233 error!("Flow not found: {}", id);
234 StatusCode::NOT_FOUND
235 })?;
236
237 drop(flows); let initial_variables = request.variables;
240 let mut executor = FlowExecutor::with_variables(initial_variables);
241 let result = executor.execute(&flow).await.map_err(|e| {
242 error!("Failed to execute flow {}: {}", id, e);
243 StatusCode::INTERNAL_SERVER_ERROR
244 })?;
245
246 info!("Executed flow: {}", id);
247 Ok(Json(result))
248}
249
250pub async fn create_flow_variant(
254 State(state): State<ScenarioStudioState>,
255 Path(id): Path<String>,
256 Json(request): Json<CreateFlowVariantRequest>,
257) -> Result<Json<FlowVariant>, StatusCode> {
258 let flows = state.flows.read().await;
260 if !flows.contains_key(&id) {
261 error!("Base flow not found: {}", id);
262 return Err(StatusCode::NOT_FOUND);
263 }
264 drop(flows);
265
266 let mut variant = FlowVariant::new(request.name, id.clone());
267 variant.description = request.description;
268
269 let variant_id = variant.id.clone();
270 let mut variants = state.variants.write().await;
271 variants.insert(variant_id.clone(), variant.clone());
272
273 info!("Created flow variant: {} for flow: {}", variant_id, id);
274 Ok(Json(variant))
275}
276
277pub async fn list_flow_variants(
281 State(state): State<ScenarioStudioState>,
282 Path(id): Path<String>,
283) -> Result<Json<Vec<FlowVariant>>, StatusCode> {
284 let variants = state.variants.read().await;
285 let flow_variants: Vec<FlowVariant> =
286 variants.values().filter(|v| v.flow_id == id).cloned().collect();
287 Ok(Json(flow_variants))
288}
289
290pub fn scenario_studio_router(state: ScenarioStudioState) -> axum::Router {
292 use axum::routing::{get, post};
293 use axum::Router;
294
295 Router::new()
296 .route("/api/v1/scenario-studio/flows", post(create_flow).get(list_flows))
297 .route(
298 "/api/v1/scenario-studio/flows/{id}",
299 get(get_flow).put(update_flow).delete(delete_flow),
300 )
301 .route("/api/v1/scenario-studio/flows/{id}/execute", post(execute_flow))
302 .route(
303 "/api/v1/scenario-studio/flows/{id}/variants",
304 post(create_flow_variant).get(list_flow_variants),
305 )
306 .with_state(state)
307}