1use crate::error::Result;
4use crate::param_overrides::OperationOverrides;
5use crate::spec_parser::ApiOperation;
6use openapiv3::{
7 MediaType, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, RequestBody,
8 Schema, SchemaKind, Type,
9};
10use serde_json::{json, Value};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone)]
15pub struct RequestTemplate {
16 pub operation: ApiOperation,
17 pub path_params: HashMap<String, String>,
18 pub query_params: HashMap<String, String>,
19 pub headers: HashMap<String, String>,
20 pub body: Option<Value>,
21}
22
23impl RequestTemplate {
24 pub fn generate_path(&self) -> String {
26 let mut path = self.operation.path.clone();
27
28 for (key, value) in &self.path_params {
29 path = path.replace(&format!("{{{}}}", key), value);
30 }
31
32 if !self.query_params.is_empty() {
33 let query_string: Vec<String> =
34 self.query_params.iter().map(|(k, v)| format!("{}={}", k, v)).collect();
35 path = format!("{}?{}", path, query_string.join("&"));
36 }
37
38 path
39 }
40
41 pub fn get_headers(&self) -> HashMap<String, String> {
43 let mut headers = self.headers.clone();
44
45 if self.body.is_some() {
46 headers
47 .entry("Content-Type".to_string())
48 .or_insert_with(|| "application/json".to_string());
49 }
50
51 headers
52 }
53}
54
55pub struct RequestGenerator;
57
58impl RequestGenerator {
59 pub fn generate_template(operation: &ApiOperation) -> Result<RequestTemplate> {
61 Self::generate_template_with_overrides(operation, None)
62 }
63
64 pub fn generate_template_with_overrides(
69 operation: &ApiOperation,
70 overrides: Option<&OperationOverrides>,
71 ) -> Result<RequestTemplate> {
72 let mut template = RequestTemplate {
73 operation: operation.clone(),
74 path_params: HashMap::new(),
75 query_params: HashMap::new(),
76 headers: HashMap::new(),
77 body: None,
78 };
79
80 for param_ref in &operation.operation.parameters {
82 if let ReferenceOr::Item(param) = param_ref {
83 Self::process_parameter_with_overrides(param, &mut template, overrides)?;
84 }
85 }
86
87 if let Some(ovr) = overrides {
89 for (name, value) in &ovr.path_params {
91 template.path_params.entry(name.clone()).or_insert_with(|| value.clone());
92 }
93 for (name, value) in &ovr.query_params {
95 template.query_params.entry(name.clone()).or_insert_with(|| value.clone());
96 }
97 for (name, value) in &ovr.headers {
99 template.headers.entry(name.clone()).or_insert_with(|| value.clone());
100 }
101 }
102
103 if let Some(ovr) = overrides {
105 if let Some(body) = ovr.get_body() {
106 template.body = Some(body.clone());
107 } else if let Some(ReferenceOr::Item(request_body)) = &operation.operation.request_body
108 {
109 template.body = Self::generate_body(request_body)?;
110 }
111 } else if let Some(ReferenceOr::Item(request_body)) = &operation.operation.request_body {
112 template.body = Self::generate_body(request_body)?;
113 }
114
115 Ok(template)
116 }
117
118 fn process_parameter(param: &Parameter, template: &mut RequestTemplate) -> Result<()> {
120 Self::process_parameter_with_overrides(param, template, None)
121 }
122
123 fn process_parameter_with_overrides(
125 param: &Parameter,
126 template: &mut RequestTemplate,
127 overrides: Option<&OperationOverrides>,
128 ) -> Result<()> {
129 let (param_type, param_data) = match param {
130 Parameter::Query { parameter_data, .. } => ("query", parameter_data),
131 Parameter::Path { parameter_data, .. } => ("path", parameter_data),
132 Parameter::Header { parameter_data, .. } => ("header", parameter_data),
133 Parameter::Cookie { parameter_data, .. } => ("cookie", parameter_data),
134 };
135
136 let value = if let Some(ovr) = overrides {
138 match param_type {
139 "path" => ovr.get_path_param(¶m_data.name).cloned(),
140 "query" => ovr.get_query_param(¶m_data.name).cloned(),
141 "header" => ovr.get_header(¶m_data.name).cloned(),
142 _ => None,
143 }
144 } else {
145 None
146 }
147 .unwrap_or_else(|| Self::generate_param_value(param_data).unwrap_or_default());
148
149 match param_type {
150 "query" => {
151 template.query_params.insert(param_data.name.clone(), value);
152 }
153 "path" => {
154 template.path_params.insert(param_data.name.clone(), value);
155 }
156 "header" => {
157 template.headers.insert(param_data.name.clone(), value);
158 }
159 "cookie" => {
160 let cookie_pair = format!("{}={}", param_data.name, value);
162 template
163 .headers
164 .entry("Cookie".to_string())
165 .and_modify(|existing| {
166 existing.push_str("; ");
167 existing.push_str(&cookie_pair);
168 })
169 .or_insert(cookie_pair);
170 }
171 _ => {}
172 }
173
174 Ok(())
175 }
176
177 fn generate_param_value(param_data: &ParameterData) -> Result<String> {
179 if let Some(example) = ¶m_data.example {
181 return Ok(example.to_string().trim_matches('"').to_string());
182 }
183
184 if let ParameterSchemaOrContent::Schema(ReferenceOr::Item(schema)) = ¶m_data.format {
186 return Ok(Self::generate_value_from_schema(schema));
187 }
188
189 Ok(Self::default_param_value(¶m_data.name))
191 }
192
193 fn default_param_value(name: &str) -> String {
195 match name.to_lowercase().as_str() {
196 "id" => "1".to_string(),
197 "limit" => "10".to_string(),
198 "offset" => "0".to_string(),
199 "page" => "1".to_string(),
200 "sort" => "name".to_string(),
201 _ => "test-value".to_string(),
202 }
203 }
204
205 fn generate_body(request_body: &RequestBody) -> Result<Option<Value>> {
207 if let Some(content) = request_body.content.get("application/json") {
209 return Ok(Some(Self::generate_json_body(content)));
210 }
211
212 Ok(None)
213 }
214
215 fn generate_json_body(media_type: &MediaType) -> Value {
217 if let Some(example) = &media_type.example {
219 return example.clone();
220 }
221
222 if let Some(ReferenceOr::Item(schema)) = &media_type.schema {
224 return Self::generate_json_from_schema(schema);
225 }
226
227 json!({})
228 }
229
230 fn generate_json_from_schema(schema: &Schema) -> Value {
232 match &schema.schema_kind {
233 SchemaKind::Type(Type::Object(obj)) => {
234 let mut map = serde_json::Map::new();
235
236 for (key, schema_ref) in &obj.properties {
237 if let ReferenceOr::Item(prop_schema) = schema_ref {
238 map.insert(key.clone(), Self::generate_json_from_schema(prop_schema));
239 }
240 }
241
242 Value::Object(map)
243 }
244 SchemaKind::Type(Type::Array(arr)) => {
245 if let Some(ReferenceOr::Item(item_schema)) = &arr.items {
246 return json!([Self::generate_json_from_schema(item_schema)]);
247 }
248 json!([])
249 }
250 SchemaKind::Type(Type::String(_)) => Self::generate_string_value(schema),
251 SchemaKind::Type(Type::Number(_)) => json!(42.0),
252 SchemaKind::Type(Type::Integer(_)) => json!(42),
253 SchemaKind::Type(Type::Boolean(_)) => json!(true),
254 _ => json!(null),
255 }
256 }
257
258 fn generate_string_value(schema: &Schema) -> Value {
260 if let Some(example) = &schema.schema_data.example {
262 return example.clone();
263 }
264
265 json!("test-string")
266 }
267
268 fn generate_value_from_schema(schema: &Schema) -> String {
270 match &schema.schema_kind {
271 SchemaKind::Type(Type::String(_)) => "test-value".to_string(),
272 SchemaKind::Type(Type::Number(_)) => "42.0".to_string(),
273 SchemaKind::Type(Type::Integer(_)) => "42".to_string(),
274 SchemaKind::Type(Type::Boolean(_)) => "true".to_string(),
275 _ => "test-value".to_string(),
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use openapiv3::Operation;
284
285 #[test]
286 fn test_generate_path() {
287 let op = ApiOperation {
288 method: "get".to_string(),
289 path: "/users/{id}".to_string(),
290 operation: Operation::default(),
291 operation_id: None,
292 };
293
294 let mut template = RequestTemplate {
295 operation: op,
296 path_params: HashMap::new(),
297 query_params: HashMap::new(),
298 headers: HashMap::new(),
299 body: None,
300 };
301
302 template.path_params.insert("id".to_string(), "123".to_string());
303 template.query_params.insert("limit".to_string(), "10".to_string());
304
305 let path = template.generate_path();
306 assert_eq!(path, "/users/123?limit=10");
307 }
308
309 #[test]
310 fn test_default_param_value() {
311 assert_eq!(RequestGenerator::default_param_value("id"), "1");
312 assert_eq!(RequestGenerator::default_param_value("limit"), "10");
313 assert_eq!(RequestGenerator::default_param_value("unknown"), "test-value");
314 }
315}