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 #[allow(dead_code)]
120 fn process_parameter(param: &Parameter, template: &mut RequestTemplate) -> Result<()> {
121 Self::process_parameter_with_overrides(param, template, None)
122 }
123
124 fn process_parameter_with_overrides(
126 param: &Parameter,
127 template: &mut RequestTemplate,
128 overrides: Option<&OperationOverrides>,
129 ) -> Result<()> {
130 let (param_type, param_data) = match param {
131 Parameter::Query { parameter_data, .. } => ("query", parameter_data),
132 Parameter::Path { parameter_data, .. } => ("path", parameter_data),
133 Parameter::Header { parameter_data, .. } => ("header", parameter_data),
134 Parameter::Cookie { parameter_data, .. } => ("cookie", parameter_data),
135 };
136
137 let value = if let Some(ovr) = overrides {
139 match param_type {
140 "path" => ovr.get_path_param(¶m_data.name).cloned(),
141 "query" => ovr.get_query_param(¶m_data.name).cloned(),
142 "header" => ovr.get_header(¶m_data.name).cloned(),
143 _ => None,
144 }
145 } else {
146 None
147 }
148 .unwrap_or_else(|| Self::generate_param_value(param_data).unwrap_or_default());
149
150 match param_type {
151 "query" => {
152 template.query_params.insert(param_data.name.clone(), value);
153 }
154 "path" => {
155 template.path_params.insert(param_data.name.clone(), value);
156 }
157 "header" => {
158 template.headers.insert(param_data.name.clone(), value);
159 }
160 "cookie" => {
161 let cookie_pair = format!("{}={}", param_data.name, value);
163 template
164 .headers
165 .entry("Cookie".to_string())
166 .and_modify(|existing| {
167 existing.push_str("; ");
168 existing.push_str(&cookie_pair);
169 })
170 .or_insert(cookie_pair);
171 }
172 _ => {}
173 }
174
175 Ok(())
176 }
177
178 fn generate_param_value(param_data: &ParameterData) -> Result<String> {
180 if let Some(example) = ¶m_data.example {
182 return Ok(example.to_string().trim_matches('"').to_string());
183 }
184
185 if let ParameterSchemaOrContent::Schema(ReferenceOr::Item(schema)) = ¶m_data.format {
187 return Ok(Self::generate_value_from_schema(schema));
188 }
189
190 Ok(Self::default_param_value(¶m_data.name))
192 }
193
194 fn default_param_value(name: &str) -> String {
196 match name.to_lowercase().as_str() {
197 "id" => "1".to_string(),
198 "limit" => "10".to_string(),
199 "offset" => "0".to_string(),
200 "page" => "1".to_string(),
201 "sort" => "name".to_string(),
202 _ => "test-value".to_string(),
203 }
204 }
205
206 fn generate_body(request_body: &RequestBody) -> Result<Option<Value>> {
208 if let Some(content) = request_body.content.get("application/json") {
210 return Ok(Some(Self::generate_json_body(content)));
211 }
212
213 Ok(None)
214 }
215
216 fn generate_json_body(media_type: &MediaType) -> Value {
218 if let Some(example) = &media_type.example {
220 return example.clone();
221 }
222
223 if let Some(ReferenceOr::Item(schema)) = &media_type.schema {
225 return Self::generate_json_from_schema(schema);
226 }
227
228 json!({})
229 }
230
231 fn generate_json_from_schema(schema: &Schema) -> Value {
233 match &schema.schema_kind {
234 SchemaKind::Type(Type::Object(obj)) => {
235 let mut map = serde_json::Map::new();
236
237 for (key, schema_ref) in &obj.properties {
238 if let ReferenceOr::Item(prop_schema) = schema_ref {
239 map.insert(key.clone(), Self::generate_json_from_schema(prop_schema));
240 }
241 }
242
243 Value::Object(map)
244 }
245 SchemaKind::Type(Type::Array(arr)) => {
246 if let Some(ReferenceOr::Item(item_schema)) = &arr.items {
247 return json!([Self::generate_json_from_schema(item_schema)]);
248 }
249 json!([])
250 }
251 SchemaKind::Type(Type::String(_)) => Self::generate_string_value(schema),
252 SchemaKind::Type(Type::Number(_)) => json!(42.0),
253 SchemaKind::Type(Type::Integer(_)) => json!(42),
254 SchemaKind::Type(Type::Boolean(_)) => json!(true),
255 _ => json!(null),
256 }
257 }
258
259 fn generate_string_value(schema: &Schema) -> Value {
261 if let Some(example) = &schema.schema_data.example {
263 return example.clone();
264 }
265
266 json!("test-string")
267 }
268
269 fn generate_value_from_schema(schema: &Schema) -> String {
271 match &schema.schema_kind {
272 SchemaKind::Type(Type::String(_)) => "test-value".to_string(),
273 SchemaKind::Type(Type::Number(_)) => "42.0".to_string(),
274 SchemaKind::Type(Type::Integer(_)) => "42".to_string(),
275 SchemaKind::Type(Type::Boolean(_)) => "true".to_string(),
276 _ => "test-value".to_string(),
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use openapiv3::Operation;
285
286 #[test]
287 fn test_generate_path() {
288 let op = ApiOperation {
289 method: "get".to_string(),
290 path: "/users/{id}".to_string(),
291 operation: Operation::default(),
292 operation_id: None,
293 };
294
295 let mut template = RequestTemplate {
296 operation: op,
297 path_params: HashMap::new(),
298 query_params: HashMap::new(),
299 headers: HashMap::new(),
300 body: None,
301 };
302
303 template.path_params.insert("id".to_string(), "123".to_string());
304 template.query_params.insert("limit".to_string(), "10".to_string());
305
306 let path = template.generate_path();
307 assert_eq!(path, "/users/123?limit=10");
308 }
309
310 #[test]
311 fn test_default_param_value() {
312 assert_eq!(RequestGenerator::default_param_value("id"), "1");
313 assert_eq!(RequestGenerator::default_param_value("limit"), "10");
314 assert_eq!(RequestGenerator::default_param_value("unknown"), "test-value");
315 }
316}