mockforge_bench/
request_gen.rs1use crate::error::Result;
4use crate::spec_parser::ApiOperation;
5use openapiv3::{
6 MediaType, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, RequestBody,
7 Schema, SchemaKind, Type,
8};
9use serde_json::{json, Value};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub struct RequestTemplate {
15 pub operation: ApiOperation,
16 pub path_params: HashMap<String, String>,
17 pub query_params: HashMap<String, String>,
18 pub headers: HashMap<String, String>,
19 pub body: Option<Value>,
20}
21
22impl RequestTemplate {
23 pub fn generate_path(&self) -> String {
25 let mut path = self.operation.path.clone();
26
27 for (key, value) in &self.path_params {
28 path = path.replace(&format!("{{{}}}", key), value);
29 }
30
31 if !self.query_params.is_empty() {
32 let query_string: Vec<String> =
33 self.query_params.iter().map(|(k, v)| format!("{}={}", k, v)).collect();
34 path = format!("{}?{}", path, query_string.join("&"));
35 }
36
37 path
38 }
39
40 pub fn get_headers(&self) -> HashMap<String, String> {
42 let mut headers = self.headers.clone();
43
44 if self.body.is_some() {
45 headers
46 .entry("Content-Type".to_string())
47 .or_insert_with(|| "application/json".to_string());
48 }
49
50 headers
51 }
52}
53
54pub struct RequestGenerator;
56
57impl RequestGenerator {
58 pub fn generate_template(operation: &ApiOperation) -> Result<RequestTemplate> {
60 let mut template = RequestTemplate {
61 operation: operation.clone(),
62 path_params: HashMap::new(),
63 query_params: HashMap::new(),
64 headers: HashMap::new(),
65 body: None,
66 };
67
68 for param_ref in &operation.operation.parameters {
70 if let ReferenceOr::Item(param) = param_ref {
71 Self::process_parameter(param, &mut template)?;
72 }
73 }
74
75 if let Some(ReferenceOr::Item(request_body)) = &operation.operation.request_body {
77 template.body = Self::generate_body(request_body)?;
78 }
79
80 Ok(template)
81 }
82
83 fn process_parameter(param: &Parameter, template: &mut RequestTemplate) -> Result<()> {
85 let (name, param_data) = match param {
86 Parameter::Query { parameter_data, .. } => ("query", parameter_data),
87 Parameter::Path { parameter_data, .. } => ("path", parameter_data),
88 Parameter::Header { parameter_data, .. } => ("header", parameter_data),
89 Parameter::Cookie { .. } => return Ok(()), };
91
92 let value = Self::generate_param_value(param_data)?;
93
94 match name {
95 "query" => {
96 template.query_params.insert(param_data.name.clone(), value);
97 }
98 "path" => {
99 template.path_params.insert(param_data.name.clone(), value);
100 }
101 "header" => {
102 template.headers.insert(param_data.name.clone(), value);
103 }
104 _ => {}
105 }
106
107 Ok(())
108 }
109
110 fn generate_param_value(param_data: &ParameterData) -> Result<String> {
112 if let Some(example) = ¶m_data.example {
114 return Ok(example.to_string().trim_matches('"').to_string());
115 }
116
117 if let ParameterSchemaOrContent::Schema(ReferenceOr::Item(schema)) = ¶m_data.format {
119 return Ok(Self::generate_value_from_schema(schema));
120 }
121
122 Ok(Self::default_param_value(¶m_data.name))
124 }
125
126 fn default_param_value(name: &str) -> String {
128 match name.to_lowercase().as_str() {
129 "id" => "1".to_string(),
130 "limit" => "10".to_string(),
131 "offset" => "0".to_string(),
132 "page" => "1".to_string(),
133 "sort" => "name".to_string(),
134 _ => "test-value".to_string(),
135 }
136 }
137
138 fn generate_body(request_body: &RequestBody) -> Result<Option<Value>> {
140 if let Some(content) = request_body.content.get("application/json") {
142 return Ok(Some(Self::generate_json_body(content)));
143 }
144
145 Ok(None)
146 }
147
148 fn generate_json_body(media_type: &MediaType) -> Value {
150 if let Some(example) = &media_type.example {
152 return example.clone();
153 }
154
155 if let Some(ReferenceOr::Item(schema)) = &media_type.schema {
157 return Self::generate_json_from_schema(schema);
158 }
159
160 json!({})
161 }
162
163 fn generate_json_from_schema(schema: &Schema) -> Value {
165 match &schema.schema_kind {
166 SchemaKind::Type(Type::Object(obj)) => {
167 let mut map = serde_json::Map::new();
168
169 for (key, schema_ref) in &obj.properties {
170 if let ReferenceOr::Item(prop_schema) = schema_ref {
171 map.insert(key.clone(), Self::generate_json_from_schema(prop_schema));
172 }
173 }
174
175 Value::Object(map)
176 }
177 SchemaKind::Type(Type::Array(arr)) => {
178 if let Some(ReferenceOr::Item(item_schema)) = &arr.items {
179 return json!([Self::generate_json_from_schema(item_schema)]);
180 }
181 json!([])
182 }
183 SchemaKind::Type(Type::String(_)) => Self::generate_string_value(schema),
184 SchemaKind::Type(Type::Number(_)) => json!(42.0),
185 SchemaKind::Type(Type::Integer(_)) => json!(42),
186 SchemaKind::Type(Type::Boolean(_)) => json!(true),
187 _ => json!(null),
188 }
189 }
190
191 fn generate_string_value(schema: &Schema) -> Value {
193 if let Some(example) = &schema.schema_data.example {
195 return example.clone();
196 }
197
198 json!("test-string")
199 }
200
201 fn generate_value_from_schema(schema: &Schema) -> String {
203 match &schema.schema_kind {
204 SchemaKind::Type(Type::String(_)) => "test-value".to_string(),
205 SchemaKind::Type(Type::Number(_)) => "42.0".to_string(),
206 SchemaKind::Type(Type::Integer(_)) => "42".to_string(),
207 SchemaKind::Type(Type::Boolean(_)) => "true".to_string(),
208 _ => "test-value".to_string(),
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use openapiv3::Operation;
217
218 #[test]
219 fn test_generate_path() {
220 let op = ApiOperation {
221 method: "get".to_string(),
222 path: "/users/{id}".to_string(),
223 operation: Operation::default(),
224 operation_id: None,
225 };
226
227 let mut template = RequestTemplate {
228 operation: op,
229 path_params: HashMap::new(),
230 query_params: HashMap::new(),
231 headers: HashMap::new(),
232 body: None,
233 };
234
235 template.path_params.insert("id".to_string(), "123".to_string());
236 template.query_params.insert("limit".to_string(), "10".to_string());
237
238 let path = template.generate_path();
239 assert_eq!(path, "/users/123?limit=10");
240 }
241
242 #[test]
243 fn test_default_param_value() {
244 assert_eq!(RequestGenerator::default_param_value("id"), "1");
245 assert_eq!(RequestGenerator::default_param_value("limit"), "10");
246 assert_eq!(RequestGenerator::default_param_value("unknown"), "test-value");
247 }
248}