1use crate::error::Result;
2use crate::generator::api_client::{extract_query_parameters, ParameterType};
3use crate::generator::swagger_parser::OperationInfo;
4use crate::generator::ts_typings::TypeScriptType;
5use crate::generator::utils::to_pascal_case;
6use crate::generator::zod_schema::ZodSchema;
7use crate::templates::context::{Field, TypeContext, ZodContext};
8use crate::templates::engine::TemplateEngine;
9use crate::templates::registry::TemplateId;
10use openapiv3::OpenAPI;
11use std::collections::HashMap;
12
13pub struct QueryParamsGenerationResult {
14 pub types: Vec<TypeScriptType>,
15 pub zod_schemas: Vec<ZodSchema>,
16}
17
18pub struct QueryParamsContext<'a> {
20 pub openapi: &'a OpenAPI,
21 pub operations: &'a [OperationInfo],
22 pub enum_registry: &'a mut HashMap<String, String>,
23 pub template_engine: Option<&'a TemplateEngine>,
24 pub spec_name: Option<&'a str>,
25 pub existing_types: &'a [TypeScriptType],
26 pub existing_zod_schemas: &'a [ZodSchema],
27}
28
29pub fn generate_query_params_for_module(
31 ctx: QueryParamsContext,
32) -> Result<QueryParamsGenerationResult> {
33 let QueryParamsContext {
34 openapi,
35 operations,
36 enum_registry,
37 template_engine,
38 spec_name,
39 existing_types,
40 existing_zod_schemas,
41 } = ctx;
42 let mut types = Vec::new();
43 let mut zod_schemas = Vec::new();
44
45 let mut existing_type_names = std::collections::HashSet::new();
47 for t in existing_types {
48 if let Some(name) = extract_type_name(&t.content) {
50 existing_type_names.insert(name);
51 }
52 }
53
54 let mut existing_schema_names = std::collections::HashSet::new();
56 for z in existing_zod_schemas {
57 if let Some(name) = extract_schema_name(&z.content) {
59 existing_schema_names.insert(name);
60 }
61 }
62
63 fn extract_type_name(content: &str) -> Option<String> {
65 content.find("export type ").and_then(|start| {
66 let after_export = &content[start + 12..];
67 after_export
68 .find([' ', '=', '{'])
69 .map(|end| after_export[..end].trim().to_string())
70 })
71 }
72
73 fn extract_schema_name(content: &str) -> Option<String> {
75 content.find("export const ").and_then(|start| {
76 let after_export = &content[start + 13..];
77 after_export
78 .find([' ', '=', ':'])
79 .map(|end| after_export[..end].trim().to_string())
80 })
81 }
82
83 for op_info in operations {
84 let operation = &op_info.operation;
85 let query_params = extract_query_parameters(openapi, operation, enum_registry)?;
86
87 if query_params.is_empty() {
88 continue;
89 }
90
91 let func_name = operation.operation_id.clone().unwrap_or_else(|| {
93 format!(
95 "{}{}",
96 to_pascal_case(&op_info.method),
97 to_pascal_case(
98 &op_info
99 .path
100 .replace("/", "")
101 .replace("{", "")
102 .replace("}", "")
103 )
104 )
105 });
106 let type_name_base = to_pascal_case(&func_name);
107 let query_type_name = format!("{}QueryParams", type_name_base);
108 let schema_name = query_type_name.clone();
110
111 let mut enum_types_to_generate = Vec::new();
115 let mut generated_enum_names = std::collections::HashSet::new();
116
117 for param in &query_params {
118 if let ParameterType::Enum(enum_name) = ¶m.param_type {
119 if let Some(enum_values) = ¶m.enum_values {
120 let enum_schema_name = format!("{}Schema", enum_name);
122 if !generated_enum_names.contains(enum_name)
123 && !existing_type_names.contains(enum_name)
124 && !existing_schema_names.contains(&enum_schema_name)
125 {
126 enum_types_to_generate.push((enum_name.clone(), enum_values.clone()));
127 generated_enum_names.insert(enum_name.clone());
128 let enum_key = enum_values.join(",");
130 enum_registry.insert(enum_key, enum_name.clone());
131 }
132 }
133 }
134 }
135
136 for (enum_name, enum_values) in &enum_types_to_generate {
138 let enum_type_content = format!(
140 "export type {} =\n{}\n;",
141 enum_name,
142 enum_values
143 .iter()
144 .map(|v| format!("\"{}\"", v))
145 .collect::<Vec<_>>()
146 .join(" |\n")
147 );
148 types.push(TypeScriptType {
149 content: enum_type_content,
150 });
151
152 if let Some(engine) = template_engine {
156 let zod_context = ZodContext {
157 schema_name: enum_name.clone(),
158 zod_expr: format!(
159 "z.enum([{}])",
160 enum_values
161 .iter()
162 .map(|v| format!("\"{}\"", v))
163 .collect::<Vec<_>>()
164 .join(", ")
165 ),
166 is_enum: true,
167 enum_values: Some(enum_values.clone()),
168 description: None,
169 needs_type_annotation: false,
170 spec_name: spec_name.map(|s| s.to_string()),
171 };
172 let zod_content = engine.render(TemplateId::ZodEnum, &zod_context)?;
173 zod_schemas.push(ZodSchema {
174 content: zod_content,
175 });
176 } else {
177 let enum_values_str = enum_values
179 .iter()
180 .map(|v| format!("\"{}\"", v))
181 .collect::<Vec<_>>()
182 .join(", ");
183 zod_schemas.push(ZodSchema {
184 content: format!(
185 "export const {}Schema = z.enum([{}]);",
186 enum_name, enum_values_str
187 ),
188 });
189 }
190 }
191
192 let mut fields = Vec::new();
194 for param in &query_params {
195 let param_type = match ¶m.param_type {
196 ParameterType::Enum(enum_name) => enum_name.clone(),
197 ParameterType::Array(item_type) => format!("{}[]", item_type),
198 ParameterType::String => "string".to_string(),
199 ParameterType::Number => "number".to_string(),
200 ParameterType::Integer => "number".to_string(),
201 ParameterType::Boolean => "boolean".to_string(),
202 };
203
204 fields.push(Field {
205 name: param.name.clone(),
206 type_name: param_type,
207 optional: true,
208 description: param.description.clone(),
209 });
210 }
211
212 if let Some(engine) = template_engine {
214 let context = TypeContext::interface(
215 query_type_name.clone(),
216 fields,
217 None,
218 spec_name.map(|s| s.to_string()),
219 );
220 let content = engine.render(TemplateId::TypeInterface, &context)?;
221 types.push(TypeScriptType { content });
222 } else {
223 let mut field_strings = Vec::new();
225 for field in &fields {
226 let desc = field
227 .description
228 .as_ref()
229 .map(|d| format!(" /**\n * {}\n */\n ", d))
230 .unwrap_or_default();
231 field_strings.push(format!("{}{}?: {};", desc, field.name, field.type_name));
232 }
233 types.push(TypeScriptType {
234 content: format!(
235 "export interface {} {{\n{}\n}}",
236 query_type_name,
237 field_strings.join("\n")
238 ),
239 });
240 }
241
242 let mut zod_field_strings = Vec::new();
244 for param in &query_params {
245 let zod_type = match ¶m.param_type {
246 ParameterType::Enum(enum_name) => {
247 format!("{}Schema", enum_name)
251 }
252 ParameterType::Array(item_type) => {
253 match item_type.as_str() {
254 "string" => "z.array(z.string())".to_string(),
255 "number" => "z.array(z.number())".to_string(),
256 "boolean" => "z.array(z.boolean())".to_string(),
257 _ => "z.array(z.any())".to_string(), }
259 }
260 ParameterType::String => "z.string()".to_string(),
261 ParameterType::Number => "z.number()".to_string(),
262 ParameterType::Integer => "z.number()".to_string(),
263 ParameterType::Boolean => "z.boolean()".to_string(),
264 };
265
266 let optional_zod = format!("{}.optional()", zod_type);
267 zod_field_strings.push(format!(" {}: {},", param.name, optional_zod));
268 }
269
270 let zod_expr = format!("z.object({{\n{}\n}})", zod_field_strings.join("\n"));
271
272 if let Some(engine) = template_engine {
274 let zod_context = ZodContext {
276 schema_name: schema_name.clone(),
277 zod_expr,
278 is_enum: false,
279 enum_values: None,
280 description: None,
281 needs_type_annotation: false,
282 spec_name: spec_name.map(|s| s.to_string()),
283 };
284 let zod_content = engine.render(TemplateId::ZodSchema, &zod_context)?;
285 zod_schemas.push(ZodSchema {
286 content: zod_content,
287 });
288 } else {
289 zod_schemas.push(ZodSchema {
291 content: format!("export const {} = {};", schema_name, zod_expr),
292 });
293 }
294 }
295
296 Ok(QueryParamsGenerationResult { types, zod_schemas })
297}