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