Skip to content

Commit 3ffbe37

Browse files
committed
Rm unknown query
1 parent 5ea33c6 commit 3ffbe37

3 files changed

Lines changed: 78 additions & 28 deletions

File tree

crates/vespera_macro/src/parser.rs

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub fn parse_function_parameter(
101101
if is_map_type(inner_ty) {
102102
return None;
103103
}
104-
104+
105105
// Check if it's a struct - expand to individual parameters
106106
if let Some(struct_params) = parse_query_struct_to_parameters(
107107
inner_ty,
@@ -110,7 +110,13 @@ pub fn parse_function_parameter(
110110
) {
111111
return Some(struct_params);
112112
}
113-
113+
114+
// Check if it's a known type (primitive or known schema)
115+
// If unknown, don't add parameter
116+
if !is_known_type(inner_ty, known_schemas, struct_definitions) {
117+
return None;
118+
}
119+
114120
// Otherwise, treat as single parameter
115121
return Some(vec![Parameter {
116122
name: param_name.clone(),
@@ -205,6 +211,55 @@ fn is_map_type(ty: &Type) -> bool {
205211
false
206212
}
207213

214+
/// Check if a type is a known type (primitive, known schema, or struct definition)
215+
fn is_known_type(
216+
ty: &Type,
217+
known_schemas: &HashMap<String, String>,
218+
struct_definitions: &HashMap<String, String>,
219+
) -> bool {
220+
// Check if it's a primitive type
221+
if is_primitive_type(ty) {
222+
return true;
223+
}
224+
225+
// Check if it's a known struct
226+
if let Type::Path(type_path) = ty {
227+
let path = &type_path.path;
228+
if path.segments.is_empty() {
229+
return false;
230+
}
231+
232+
let segment = path.segments.last().unwrap();
233+
let ident_str = segment.ident.to_string();
234+
235+
// Get type name (handle both simple and qualified paths)
236+
let type_name = if path.segments.len() > 1 {
237+
ident_str.clone()
238+
} else {
239+
ident_str.clone()
240+
};
241+
242+
// Check if it's in struct_definitions or known_schemas
243+
if struct_definitions.contains_key(&type_name) || known_schemas.contains_key(&type_name) {
244+
return true;
245+
}
246+
247+
// Check for generic types like Vec<T>, Option<T> - recursively check inner type
248+
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
249+
match ident_str.as_str() {
250+
"Vec" | "Option" => {
251+
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
252+
return is_known_type(inner_ty, known_schemas, struct_definitions);
253+
}
254+
}
255+
_ => {}
256+
}
257+
}
258+
}
259+
260+
false
261+
}
262+
208263
/// Parse struct fields to individual query parameters
209264
/// Returns None if the type is not a struct or cannot be parsed
210265
fn parse_query_struct_to_parameters(
@@ -218,43 +273,43 @@ fn parse_query_struct_to_parameters(
218273
if path.segments.is_empty() {
219274
return None;
220275
}
221-
276+
222277
let segment = path.segments.last().unwrap();
223278
let ident_str = segment.ident.to_string();
224-
279+
225280
// Get type name (handle both simple and qualified paths)
226281
let type_name = if path.segments.len() > 1 {
227282
ident_str.clone()
228283
} else {
229284
ident_str.clone()
230285
};
231-
286+
232287
// Check if it's a known struct
233288
if let Some(struct_def) = struct_definitions.get(&type_name) {
234289
if let Ok(struct_item) = syn::parse_str::<syn::ItemStruct>(struct_def) {
235290
let mut parameters = Vec::new();
236-
291+
237292
// Extract rename_all attribute from struct
238293
let rename_all = extract_rename_all(&struct_item.attrs);
239-
294+
240295
if let syn::Fields::Named(fields_named) = &struct_item.fields {
241296
for field in &fields_named.named {
242297
let rust_field_name = field
243298
.ident
244299
.as_ref()
245300
.map(|i| i.to_string())
246301
.unwrap_or_else(|| "unknown".to_string());
247-
302+
248303
// Check for field-level rename attribute first (takes precedence)
249304
let field_name = if let Some(renamed) = extract_field_rename(&field.attrs) {
250305
renamed
251306
} else {
252307
// Apply rename_all transformation if present
253308
rename_field(&rust_field_name, rename_all.as_deref())
254309
};
255-
310+
256311
let field_type = &field.ty;
257-
312+
258313
// Check if field is Option<T>
259314
let is_optional = matches!(
260315
field_type,
@@ -266,22 +321,26 @@ fn parse_query_struct_to_parameters(
266321
.map(|s| s.ident == "Option")
267322
.unwrap_or(false)
268323
);
269-
324+
270325
// Parse field type to schema (inline, not ref)
271326
// For Query parameters, we need inline schemas, not refs
272327
let mut field_schema = parse_type_to_schema_ref_with_schemas(
273328
field_type,
274329
known_schemas,
275330
struct_definitions,
276331
);
277-
332+
278333
// Convert ref to inline if needed (Query parameters should not use refs)
279334
// If it's a ref to a known struct, get the struct definition and inline it
280335
if let SchemaRef::Ref(ref_ref) = &field_schema {
281336
// Try to extract type name from ref path (e.g., "#/components/schemas/User" -> "User")
282-
if let Some(type_name) = ref_ref.ref_path.strip_prefix("#/components/schemas/") {
337+
if let Some(type_name) =
338+
ref_ref.ref_path.strip_prefix("#/components/schemas/")
339+
{
283340
if let Some(struct_def) = struct_definitions.get(type_name) {
284-
if let Ok(nested_struct_item) = syn::parse_str::<syn::ItemStruct>(struct_def) {
341+
if let Ok(nested_struct_item) =
342+
syn::parse_str::<syn::ItemStruct>(struct_def)
343+
{
285344
// Parse the nested struct to schema (inline)
286345
let nested_schema = parse_struct_to_schema(
287346
&nested_struct_item,
@@ -293,7 +352,7 @@ fn parse_query_struct_to_parameters(
293352
}
294353
}
295354
}
296-
355+
297356
// If it's Option<T>, make it nullable
298357
let final_schema = if is_optional {
299358
if let SchemaRef::Inline(mut schema) = field_schema {
@@ -316,9 +375,9 @@ fn parse_query_struct_to_parameters(
316375
SchemaRef::Inline(schema) => SchemaRef::Inline(schema),
317376
}
318377
};
319-
378+
320379
let required = !is_optional;
321-
380+
322381
parameters.push(Parameter {
323382
name: field_name,
324383
r#in: ParameterLocation::Query,
@@ -329,7 +388,7 @@ fn parse_query_struct_to_parameters(
329388
});
330389
}
331390
}
332-
391+
333392
if !parameters.is_empty() {
334393
return Some(parameters);
335394
}

examples/axum-example/openapi.json

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -495,16 +495,6 @@
495495
"/no-schema-query": {
496496
"get": {
497497
"operationId": "mod_file_with_no_schema_query",
498-
"parameters": [
499-
{
500-
"name": "query",
501-
"in": "query",
502-
"required": true,
503-
"schema": {
504-
"type": "object"
505-
}
506-
}
507-
],
508498
"responses": {
509499
"200": {
510500
"description": "Successful response",

examples/axum-example/src/routes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub struct NoSchemaQuery {
4747
pub age: u32,
4848
pub optional_age: Option<u32>,
4949
}
50+
5051
#[vespera::route(get, path = "/no-schema-query")]
5152
pub async fn mod_file_with_no_schema_query(Query(query): Query<NoSchemaQuery>) -> &'static str {
5253
println!("no schema query: {:?}", query.age);

0 commit comments

Comments
 (0)