1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! Scenario type definitions
//!
//! Pure-data types for high-level business workflows. Hoisted from
//! `mockforge_core::scenarios::types` to foundation so that crates which
//! cannot depend on `mockforge-core` (e.g. `mockforge-intelligence`, per the
//! cycle broken by #562 phase 1) can still consume `ScenarioDefinition` /
//! `ScenarioStep`. This unblocks moving the `behavioral_cloning` handler
//! out of `mockforge-http` (#555 phase 5+).
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Scenario definition for high-level business workflows
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenarioDefinition {
/// Unique scenario identifier (e.g., "checkout-success")
pub id: String,
/// Human-readable name (e.g., "CheckoutSuccess")
pub name: String,
/// Scenario description
pub description: Option<String>,
/// Ordered list of API calls to execute
pub steps: Vec<ScenarioStep>,
/// Default variables for the scenario
pub variables: HashMap<String, serde_json::Value>,
/// Input parameters for the scenario
pub parameters: Vec<ScenarioParameter>,
/// Tags for categorization
pub tags: Vec<String>,
}
/// A single step in a scenario (represents one API call)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenarioStep {
/// Step identifier
pub id: String,
/// Step name/description
pub name: String,
/// HTTP method
pub method: String,
/// API endpoint path
pub path: String,
/// Request body (can use template variables)
pub body: Option<serde_json::Value>,
/// Request headers
pub headers: HashMap<String, String>,
/// Query parameters
pub query_params: HashMap<String, String>,
/// Path parameters (for dynamic paths)
pub path_params: HashMap<String, String>,
/// Variables to extract from response (for use in subsequent steps)
pub extract: HashMap<String, String>, // variable_name -> json_path
/// Expected status code
pub expected_status: Option<u16>,
/// Whether to continue on failure
pub continue_on_failure: bool,
/// Delay before executing this step (in milliseconds)
pub delay_ms: Option<u64>,
/// Dependencies on other steps (step IDs that must complete first)
pub depends_on: Vec<String>,
}
/// Input parameter for a scenario
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenarioParameter {
/// Parameter name
pub name: String,
/// Parameter description
pub description: Option<String>,
/// Parameter type (e.g., "string", "number", "object")
pub parameter_type: String,
/// Whether parameter is required
pub required: bool,
/// Default value (if optional)
pub default: Option<serde_json::Value>,
}
/// Result of scenario execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenarioResult {
/// Scenario ID that was executed
pub scenario_id: String,
/// Whether scenario completed successfully
pub success: bool,
/// Results from each step
pub step_results: Vec<StepResult>,
/// Total execution time in milliseconds
pub duration_ms: u64,
/// Error message (if scenario failed)
pub error: Option<String>,
/// Final state (all variables after execution)
pub final_state: HashMap<String, serde_json::Value>,
}
/// Result of a single step execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StepResult {
/// Step ID
pub step_id: String,
/// Whether step succeeded
pub success: bool,
/// HTTP status code
pub status_code: Option<u16>,
/// Response body
pub response_body: Option<serde_json::Value>,
/// Extracted variables from this step
pub extracted_variables: HashMap<String, serde_json::Value>,
/// Error message (if step failed)
pub error: Option<String>,
/// Execution time in milliseconds
pub duration_ms: u64,
}
impl ScenarioDefinition {
/// Create a new scenario definition
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: id.into(),
name: name.into(),
description: None,
steps: Vec::new(),
variables: HashMap::new(),
parameters: Vec::new(),
tags: Vec::new(),
}
}
/// Add a step to the scenario
pub fn add_step(mut self, step: ScenarioStep) -> Self {
self.steps.push(step);
self
}
/// Add a parameter to the scenario
pub fn add_parameter(mut self, param: ScenarioParameter) -> Self {
self.parameters.push(param);
self
}
/// Set default variables
pub fn with_variables(mut self, variables: HashMap<String, serde_json::Value>) -> Self {
self.variables = variables;
self
}
/// Add tags
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
}
impl ScenarioStep {
/// Create a new scenario step
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
method: impl Into<String>,
path: impl Into<String>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
method: method.into(),
path: path.into(),
body: None,
headers: HashMap::new(),
query_params: HashMap::new(),
path_params: HashMap::new(),
extract: HashMap::new(),
expected_status: None,
continue_on_failure: false,
delay_ms: None,
depends_on: Vec::new(),
}
}
/// Set request body
pub fn with_body(mut self, body: serde_json::Value) -> Self {
self.body = Some(body);
self
}
/// Add a variable extraction rule
pub fn extract_variable(
mut self,
var_name: impl Into<String>,
json_path: impl Into<String>,
) -> Self {
self.extract.insert(var_name.into(), json_path.into());
self
}
/// Set expected status code
pub fn expect_status(mut self, status: u16) -> Self {
self.expected_status = Some(status);
self
}
/// Add a dependency on another step
pub fn depends_on(mut self, step_id: impl Into<String>) -> Self {
self.depends_on.push(step_id.into());
self
}
}