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
222pub async fn execute_flow(
226 Path(id): Path<String>,
227 State(state): State<ScenarioStudioState>,
228 Json(request): Json<ExecuteFlowRequest>,
229) -> Result<Json<FlowExecutionResult>, StatusCode> {
230 let flows = state.flows.read().await;
231 let flow = flows.get(&id).cloned().ok_or_else(|| {
232 error!("Flow not found: {}", id);
233 StatusCode::NOT_FOUND
234 })?;
235
236 drop(flows); let initial_variables = request.variables;
239 let mut executor = FlowExecutor::with_variables(initial_variables);
240 let result = executor.execute(&flow).await.map_err(|e| {
241 error!("Failed to execute flow {}: {}", id, e);
242 StatusCode::INTERNAL_SERVER_ERROR
243 })?;
244
245 info!("Executed flow: {}", id);
246 Ok(Json(result))
247}
248
249pub async fn create_flow_variant(
253 State(state): State<ScenarioStudioState>,
254 Path(id): Path<String>,
255 Json(request): Json<CreateFlowVariantRequest>,
256) -> Result<Json<FlowVariant>, StatusCode> {
257 let flows = state.flows.read().await;
259 if !flows.contains_key(&id) {
260 error!("Base flow not found: {}", id);
261 return Err(StatusCode::NOT_FOUND);
262 }
263 drop(flows);
264
265 let mut variant = FlowVariant::new(request.name, id.clone());
266 variant.description = request.description;
267
268 let variant_id = variant.id.clone();
269 let mut variants = state.variants.write().await;
270 variants.insert(variant_id.clone(), variant.clone());
271
272 info!("Created flow variant: {} for flow: {}", variant_id, id);
273 Ok(Json(variant))
274}
275
276pub async fn list_flow_variants(
280 State(state): State<ScenarioStudioState>,
281 Path(id): Path<String>,
282) -> Result<Json<Vec<FlowVariant>>, StatusCode> {
283 let variants = state.variants.read().await;
284 let flow_variants: Vec<FlowVariant> =
285 variants.values().filter(|v| v.flow_id == id).cloned().collect();
286 Ok(Json(flow_variants))
287}
288
289pub fn scenario_studio_router(state: ScenarioStudioState) -> axum::Router {
291 use axum::routing::{get, post};
292 use axum::Router;
293
294 Router::new()
295 .route("/api/v1/scenario-studio/flows", post(create_flow).get(list_flows))
296 .route(
297 "/api/v1/scenario-studio/flows/{id}",
298 get(get_flow).put(update_flow).delete(delete_flow),
299 )
300 .route("/api/v1/scenario-studio/flows/{id}/execute", post(execute_flow))
301 .route(
302 "/api/v1/scenario-studio/flows/{id}/variants",
303 post(create_flow_variant).get(list_flow_variants),
304 )
305 .with_state(state)
306}