Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isQueryParam}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{#swagger2AnnotationLibrary}}@Parameter(description = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}{{#defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]{{^isContainer}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/isContainer}}){{/defaultValue}}{{/allowableValues}}{{#allowableValues}}{{^defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]){{/defaultValue}}{{/allowableValues}}{{^allowableValues}}{{#defaultValue}}{{^isContainer}}, schema = Schema(defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}){{/isContainer}}{{/defaultValue}}{{/allowableValues}}) {{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}, allowableValues = "{{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}"{{/allowableValues}}{{^isContainer}}{{#defaultValue}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/defaultValue}}{{/isContainer}}) {{/swagger1AnnotationLibrary}}{{#useBeanValidation}}@Valid{{/useBeanValidation}}{{^isModel}} @RequestParam(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}{{^isContainer}}{{#defaultValue}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/defaultValue}}{{/isContainer}}){{/isModel}}{{#isDate}} @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE){{/isDate}}{{#isDateTime}} @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME){{/isDateTime}} {{{paramName}}}: {{>optionalDataType}}{{/isQueryParam}}
{{#isQueryParam}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{#swagger2AnnotationLibrary}}@Parameter(description = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}{{#defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]{{^isContainer}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/isContainer}}){{/defaultValue}}{{/allowableValues}}{{#allowableValues}}{{^defaultValue}}, schema = Schema(allowableValues = [{{#values}}"{{{.}}}"{{^-last}}, {{/-last}}{{/values}}]){{/defaultValue}}{{/allowableValues}}{{^allowableValues}}{{#defaultValue}}{{^isContainer}}, schema = Schema(defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}){{/isContainer}}{{/defaultValue}}{{/allowableValues}}) {{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}, allowableValues = "{{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}"{{/allowableValues}}{{^isContainer}}{{#defaultValue}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/defaultValue}}{{/isContainer}}) {{/swagger1AnnotationLibrary}}{{#useBeanValidation}}@Valid{{/useBeanValidation}}{{^isModel}} @RequestParam(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}{{^isContainer}}{{#defaultValue}}, defaultValue = {{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{{defaultValue}}}{{^isString}}"{{/isString}}{{#isString}}{{#isEnum}}"{{/isEnum}}{{/isString}}{{/defaultValue}}{{/isContainer}}){{/isModel}}{{#isModel}} @ModelAttribute("{{baseName}}"){{/isModel}}{{#isDate}} @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE){{/isDate}}{{#isDateTime}} @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME){{/isDateTime}} {{{paramName}}}: {{>optionalDataType}}{{/isQueryParam}}
Original file line number Diff line number Diff line change
Expand Up @@ -6431,6 +6431,34 @@ public void suspendFunctionsDefaultsToFalse() throws Exception {
"suspend should not be present when suspendFunctions is not enabled");
}

// ========== MODEL QUERY PARAM @ModelAttribute TESTS ==========

@Test
public void modelQueryParamsGetModelAttributeAnnotation() throws Exception {
// Complex query params (isModel=true) must get @ModelAttribute("baseName") so that:
// - Spring binds nested fields (e.g. filter[state]=...) correctly
// - The exact wire name is preserved when paramName is sanitized
// (e.g. 'order-status' → paramName 'orderStatus'; the annotation keeps the wire name)
Map<String, Object> additionalProperties = new HashMap<>();
additionalProperties.put(INTERFACE_ONLY, "true");
additionalProperties.put(SKIP_DEFAULT_INTERFACE, "true");
additionalProperties.put(USE_TAGS, "true");

Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-sort-validation.yaml", additionalProperties);

File ordersApi = files.get("OrdersApi.kt");
// valid Kotlin identifier — @ModelAttribute still required for binding
assertFileContains(ordersApi.toPath(), "@ModelAttribute(\"filter\")");
// non-Kotlin-identifier names — wire name must be explicit in the annotation
assertFileContains(ordersApi.toPath(), "@ModelAttribute(\"order-status\")");
assertFileContains(ordersApi.toPath(), "@ModelAttribute(\"item-status\")");
// @RequestParam must NOT be used for any model param
assertFileNotContains(ordersApi.toPath(), "@RequestParam(value = \"filter\"");
assertFileNotContains(ordersApi.toPath(), "@RequestParam(value = \"order-status\"");
assertFileNotContains(ordersApi.toPath(), "@RequestParam(value = \"item-status\"");
}

@Test
public void suspendFunctionsWithServiceInterface() throws Exception {
Path root = generateApiSources(Map.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ servers:
tags:
- name: pet
description: Everything about your Pets
- name: orders
description: Order filtering endpoints
paths:
/pet/findByStatusWithSort:
get:
Expand Down Expand Up @@ -540,6 +542,40 @@ paths:
type: array
items:
$ref: '#/components/schemas/Pet'
# ---- deepObject model query param test cases ----

/orders:
get:
tags:
- orders
summary: List orders filtered by status — tests @ModelAttribute binding for model query params
operationId: listOrders
parameters:
- name: filter
in: query
description: Filter by order status (valid Kotlin identifier)
style: deepObject
explode: true
schema:
$ref: '#/components/schemas/OrderStatus'
- name: order-status
in: query
description: Filter by order status (non-Kotlin-identifier wire name, hyphenated)
style: deepObject
explode: true
schema:
$ref: '#/components/schemas/OrderStatus'
- name: item-status
in: query
description: Filter by item status (non-Kotlin-identifier wire name, hyphenated)
style: deepObject
explode: true
schema:
$ref: '#/components/schemas/OrderStatus'
responses:
'200':
description: OK

# ---- openApiNullable / 4-state required×nullable test cases ----

/nullable/check-required-only:
Expand Down Expand Up @@ -627,6 +663,11 @@ components:
- "id,desc"
- "createdAt,asc"
- "createdAt,desc"
OrderStatus:
type: object
properties:
state:
type: string
Pet:
type: object
required:
Expand Down
Loading