use crate::error::Result;
use crate::generator::api_client::{extract_query_parameters, ParameterType};
use crate::generator::swagger_parser::OperationInfo;
use crate::generator::ts_typings::TypeScriptType;
use crate::generator::utils::to_pascal_case;
use crate::generator::zod_schema::ZodSchema;
use crate::templates::context::{Field, TypeContext, ZodContext};
use crate::templates::engine::TemplateEngine;
use crate::templates::registry::TemplateId;
use openapiv3::OpenAPI;
use std::collections::HashMap;
pub struct QueryParamsGenerationResult {
pub types: Vec<TypeScriptType>,
pub zod_schemas: Vec<ZodSchema>,
}
pub struct QueryParamsContext<'a> {
pub openapi: &'a OpenAPI,
pub operations: &'a [OperationInfo],
pub enum_registry: &'a mut HashMap<String, String>,
pub template_engine: Option<&'a TemplateEngine>,
pub spec_name: Option<&'a str>,
pub existing_types: &'a [TypeScriptType],
pub existing_zod_schemas: &'a [ZodSchema],
}
pub fn generate_query_params_for_module(
ctx: QueryParamsContext,
) -> Result<QueryParamsGenerationResult> {
let QueryParamsContext {
openapi,
operations,
enum_registry,
template_engine,
spec_name,
existing_types,
existing_zod_schemas,
} = ctx;
let mut types = Vec::new();
let mut zod_schemas = Vec::new();
let mut existing_type_names = std::collections::HashSet::new();
for t in existing_types {
if let Some(name) = extract_type_name(&t.content) {
existing_type_names.insert(name);
}
}
let mut existing_schema_names = std::collections::HashSet::new();
for z in existing_zod_schemas {
if let Some(name) = extract_schema_name(&z.content) {
existing_schema_names.insert(name);
}
}
fn extract_type_name(content: &str) -> Option<String> {
content.find("export type ").and_then(|start| {
let after_export = &content[start + 12..];
after_export
.find([' ', '=', '{'])
.map(|end| after_export[..end].trim().to_string())
})
}
fn extract_schema_name(content: &str) -> Option<String> {
content.find("export const ").and_then(|start| {
let after_export = &content[start + 13..];
after_export
.find([' ', '=', ':'])
.map(|end| after_export[..end].trim().to_string())
})
}
for op_info in operations {
let operation = &op_info.operation;
let query_params = extract_query_parameters(openapi, operation, enum_registry)?;
if query_params.is_empty() {
continue;
}
let func_name = operation.operation_id.clone().unwrap_or_else(|| {
format!(
"{}{}",
to_pascal_case(&op_info.method),
to_pascal_case(
&op_info
.path
.replace("/", "")
.replace("{", "")
.replace("}", "")
)
)
});
let type_name_base = to_pascal_case(&func_name);
let query_type_name = format!("{}QueryParams", type_name_base);
let schema_name = query_type_name.clone();
let mut enum_types_to_generate = Vec::new();
let mut generated_enum_names = std::collections::HashSet::new();
for param in &query_params {
if let ParameterType::Enum(enum_name) = ¶m.param_type {
if let Some(enum_values) = ¶m.enum_values {
let enum_schema_name = format!("{}Schema", enum_name);
if !generated_enum_names.contains(enum_name)
&& !existing_type_names.contains(enum_name)
&& !existing_schema_names.contains(&enum_schema_name)
{
enum_types_to_generate.push((enum_name.clone(), enum_values.clone()));
generated_enum_names.insert(enum_name.clone());
let enum_key = enum_values.join(",");
enum_registry.insert(enum_key, enum_name.clone());
}
}
}
}
for (enum_name, enum_values) in &enum_types_to_generate {
let enum_type_content = format!(
"export type {} =\n{}\n;",
enum_name,
enum_values
.iter()
.map(|v| format!("\"{}\"", v))
.collect::<Vec<_>>()
.join(" |\n")
);
types.push(TypeScriptType {
content: enum_type_content,
});
if let Some(engine) = template_engine {
let zod_context = ZodContext {
schema_name: enum_name.clone(),
zod_expr: format!(
"z.enum([{}])",
enum_values
.iter()
.map(|v| format!("\"{}\"", v))
.collect::<Vec<_>>()
.join(", ")
),
is_enum: true,
enum_values: Some(enum_values.clone()),
description: None,
needs_type_annotation: false,
spec_name: spec_name.map(|s| s.to_string()),
};
let zod_content = engine.render(TemplateId::ZodEnum, &zod_context)?;
zod_schemas.push(ZodSchema {
content: zod_content,
});
} else {
let enum_values_str = enum_values
.iter()
.map(|v| format!("\"{}\"", v))
.collect::<Vec<_>>()
.join(", ");
zod_schemas.push(ZodSchema {
content: format!(
"export const {}Schema = z.enum([{}]);",
enum_name, enum_values_str
),
});
}
}
let mut fields = Vec::new();
for param in &query_params {
let param_type = match ¶m.param_type {
ParameterType::Enum(enum_name) => enum_name.clone(),
ParameterType::Array(item_type) => format!("{}[]", item_type),
ParameterType::String => "string".to_string(),
ParameterType::Number => "number".to_string(),
ParameterType::Integer => "number".to_string(),
ParameterType::Boolean => "boolean".to_string(),
};
fields.push(Field {
name: param.name.clone(),
type_name: param_type,
optional: true,
description: param.description.clone(),
});
}
if let Some(engine) = template_engine {
let context = TypeContext::interface(
query_type_name.clone(),
fields,
None,
spec_name.map(|s| s.to_string()),
);
let content = engine.render(TemplateId::TypeInterface, &context)?;
types.push(TypeScriptType { content });
} else {
let mut field_strings = Vec::new();
for field in &fields {
let desc = field
.description
.as_ref()
.map(|d| format!(" /**\n * {}\n */\n ", d))
.unwrap_or_default();
field_strings.push(format!("{}{}?: {};", desc, field.name, field.type_name));
}
types.push(TypeScriptType {
content: format!(
"export interface {} {{\n{}\n}}",
query_type_name,
field_strings.join("\n")
),
});
}
let mut zod_field_strings = Vec::new();
for param in &query_params {
let zod_type = match ¶m.param_type {
ParameterType::Enum(enum_name) => {
format!("{}Schema", enum_name)
}
ParameterType::Array(item_type) => {
match item_type.as_str() {
"string" => "z.array(z.string())".to_string(),
"number" => "z.array(z.number())".to_string(),
"boolean" => "z.array(z.boolean())".to_string(),
_ => "z.array(z.any())".to_string(), }
}
ParameterType::String => "z.string()".to_string(),
ParameterType::Number => "z.number()".to_string(),
ParameterType::Integer => "z.number()".to_string(),
ParameterType::Boolean => "z.boolean()".to_string(),
};
let optional_zod = format!("{}.optional()", zod_type);
zod_field_strings.push(format!(" {}: {},", param.name, optional_zod));
}
let zod_expr = format!("z.object({{\n{}\n}})", zod_field_strings.join("\n"));
if let Some(engine) = template_engine {
let zod_context = ZodContext {
schema_name: schema_name.clone(),
zod_expr,
is_enum: false,
enum_values: None,
description: None,
needs_type_annotation: false,
spec_name: spec_name.map(|s| s.to_string()),
};
let zod_content = engine.render(TemplateId::ZodSchema, &zod_context)?;
zod_schemas.push(ZodSchema {
content: zod_content,
});
} else {
zod_schemas.push(ZodSchema {
content: format!("export const {} = {};", schema_name, zod_expr),
});
}
}
Ok(QueryParamsGenerationResult { types, zod_schemas })
}