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 { .. } => return Ok(()), };
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 _ => {}
160 }
161
162 Ok(())
163 }
164
165 fn generate_param_value(param_data: &ParameterData) -> Result<String> {
167 if let Some(example) = ¶m_data.example {
169 return Ok(example.to_string().trim_matches('"').to_string());
170 }
171
172 if let ParameterSchemaOrContent::Schema(ReferenceOr::Item(schema)) = ¶m_data.format {
174 return Ok(Self::generate_value_from_schema(schema));
175 }
176
177 Ok(Self::default_param_value(¶m_data.name))
179 }
180
181 fn default_param_value(name: &str) -> String {
183 match name.to_lowercase().as_str() {
184 "id" => "1".to_string(),
185 "limit" => "10".to_string(),
186 "offset" => "0".to_string(),
187 "page" => "1".to_string(),
188 "sort" => "name".to_string(),
189 _ => "test-value".to_string(),
190 }
191 }
192
193 fn generate_body(request_body: &RequestBody) -> Result<Option<Value>> {
195 if let Some(content) = request_body.content.get("application/json") {
197 return Ok(Some(Self::generate_json_body(content)));
198 }
199
200 Ok(None)
201 }
202
203 fn generate_json_body(media_type: &MediaType) -> Value {
205 if let Some(example) = &media_type.example {
207 return example.clone();
208 }
209
210 if let Some(ReferenceOr::Item(schema)) = &media_type.schema {
212 return Self::generate_json_from_schema(schema);
213 }
214
215 json!({})
216 }
217
218 fn generate_json_from_schema(schema: &Schema) -> Value {
220 match &schema.schema_kind {
221 SchemaKind::Type(Type::Object(obj)) => {
222 let mut map = serde_json::Map::new();
223
224 for (key, schema_ref) in &obj.properties {
225 if let ReferenceOr::Item(prop_schema) = schema_ref {
226 map.insert(key.clone(), Self::generate_json_from_schema(prop_schema));
227 }
228 }
229
230 Value::Object(map)
231 }
232 SchemaKind::Type(Type::Array(arr)) => {
233 if let Some(ReferenceOr::Item(item_schema)) = &arr.items {
234 return json!([Self::generate_json_from_schema(item_schema)]);
235 }
236 json!([])
237 }
238 SchemaKind::Type(Type::String(_)) => Self::generate_string_value(schema),
239 SchemaKind::Type(Type::Number(_)) => json!(42.0),
240 SchemaKind::Type(Type::Integer(_)) => json!(42),
241 SchemaKind::Type(Type::Boolean(_)) => json!(true),
242 _ => json!(null),
243 }
244 }
245
246 fn generate_string_value(schema: &Schema) -> Value {
248 if let Some(example) = &schema.schema_data.example {
250 return example.clone();
251 }
252
253 json!("test-string")
254 }
255
256 fn generate_value_from_schema(schema: &Schema) -> String {
258 match &schema.schema_kind {
259 SchemaKind::Type(Type::String(_)) => "test-value".to_string(),
260 SchemaKind::Type(Type::Number(_)) => "42.0".to_string(),
261 SchemaKind::Type(Type::Integer(_)) => "42".to_string(),
262 SchemaKind::Type(Type::Boolean(_)) => "true".to_string(),
263 _ => "test-value".to_string(),
264 }
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use openapiv3::Operation;
272
273 #[test]
274 fn test_generate_path() {
275 let op = ApiOperation {
276 method: "get".to_string(),
277 path: "/users/{id}".to_string(),
278 operation: Operation::default(),
279 operation_id: None,
280 };
281
282 let mut template = RequestTemplate {
283 operation: op,
284 path_params: HashMap::new(),
285 query_params: HashMap::new(),
286 headers: HashMap::new(),
287 body: None,
288 };
289
290 template.path_params.insert("id".to_string(), "123".to_string());
291 template.query_params.insert("limit".to_string(), "10".to_string());
292
293 let path = template.generate_path();
294 assert_eq!(path, "/users/123?limit=10");
295 }
296
297 #[test]
298 fn test_default_param_value() {
299 assert_eq!(RequestGenerator::default_param_value("id"), "1");
300 assert_eq!(RequestGenerator::default_param_value("limit"), "10");
301 assert_eq!(RequestGenerator::default_param_value("unknown"), "test-value");
302 }
303}