Skip to content

Commit 4aadf0f

Browse files
committed
add x-operation-extra-annotation
1 parent c92887f commit 4aadf0f

4 files changed

Lines changed: 58 additions & 8 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ public void processOpts() {
483483
// Spring-specific import mappings for x-spring-paginated support
484484
importMapping.put("ApiIgnore", "springfox.documentation.annotations.ApiIgnore");
485485
importMapping.put("ParameterObject", "org.springdoc.api.annotations.ParameterObject");
486+
importMapping.put("PageableAsQueryParam", "org.springdoc.core.converters.models.PageableAsQueryParam");
486487
if (useSpringBoot3) {
487488
importMapping.put("ParameterObject", "org.springdoc.core.annotations.ParameterObject");
488489
}
@@ -924,16 +925,17 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
924925

925926
CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, servers);
926927

928+
// Check if operation has all three pagination query parameters (case-sensitive)
929+
boolean hasParamsForPageable = codegenOperation.queryParams.stream()
930+
.map(p -> p.baseName)
931+
.collect(Collectors.toSet())
932+
.containsAll(defaultPageableQueryParams);
927933
// Auto-detect pagination parameters and add x-spring-paginated if autoXSpringPaginated is enabled
928934
// Only for spring-boot library, respect manual x-spring-paginated: false setting
929935
if (SPRING_BOOT.equals(library) && autoXSpringPaginated) {
930936
// Check if x-spring-paginated is not explicitly set to false
931937
if (operation.getExtensions() == null || !Boolean.FALSE.equals(operation.getExtensions().get("x-spring-paginated"))) {
932-
// Check if operation has all three pagination query parameters (case-sensitive)
933-
boolean hasParamsForPageable = codegenOperation.queryParams.stream()
934-
.map(p -> p.baseName)
935-
.collect(Collectors.toSet())
936-
.containsAll(defaultPageableQueryParams);
938+
937939

938940
if (hasParamsForPageable) {
939941
// Automatically add x-spring-paginated to the operation
@@ -962,7 +964,8 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
962964
codegenOperation.imports.add("ApiIgnore");
963965
}
964966
if (DocumentationProvider.SPRINGDOC.equals(getDocumentationProvider())) {
965-
codegenOperation.imports.add("ParameterObject");
967+
codegenOperation.imports.add("PageableAsQueryParam");
968+
codegenOperation.vendorExtensions.put("x-operation-extra-annotation", "@PageableAsQueryParam");
966969
}
967970

968971
// #8315 Remove matching Spring Data Web default query params if 'x-spring-paginated' with Pageable is used

modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class {{classname}}Controller({{#serviceInterface}}@Autowired(required = true) v
8484
authorizations = [{{#authMethods}}Authorization(value = "{{name}}"{{#isOAuth}}, scopes = [{{#scopes}}AuthorizationScope(scope = "{{scope}}", description = "{{description}}"){{^-last}}, {{/-last}}{{/scopes}}]{{/isOAuth}}){{^-last}}, {{/-last}}{{/authMethods}}]{{/hasAuthMethods}})
8585
@ApiResponses(
8686
value = [{{#responses}}ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{.}}}::class{{/baseType}}{{#containerType}}, responseContainer = "{{{.}}}"{{/containerType}}){{^-last}},{{/-last}}{{/responses}}]){{/swagger1AnnotationLibrary}}
87+
{{#vendorExtensions.x-operation-extra-annotation}}
88+
{{{.}}}
89+
{{/vendorExtensions.x-operation-extra-annotation}}
8790
@RequestMapping(
8891
method = [RequestMethod.{{httpMethod}}],
8992
// "{{#lambdaEscapeInNormalString}}{{{path}}}{{/lambdaEscapeInNormalString}}"

modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ interface {{classname}} {
9999
@ApiResponses(
100100
value = [{{#responses}}ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{.}}}::class{{/baseType}}{{#containerType}}, responseContainer = "{{{.}}}"{{/containerType}}){{^-last}}, {{/-last}}{{/responses}}]
101101
){{/swagger1AnnotationLibrary}}
102+
{{#vendorExtensions.x-operation-extra-annotation}}
103+
{{{.}}}
104+
{{/vendorExtensions.x-operation-extra-annotation}}
102105
@RequestMapping(
103106
method = [RequestMethod.{{httpMethod}}],
104107
// "{{#lambdaEscapeInNormalString}}{{{path}}}{{/lambdaEscapeInNormalString}}"

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3678,7 +3678,6 @@ public void springPaginatedWithSpringDoc() throws Exception {
36783678

36793679
File petApi = files.get("PetApi.kt");
36803680
assertFileContains(petApi.toPath(), "import org.springframework.data.domain.Pageable");
3681-
assertFileContains(petApi.toPath(), "import org.springdoc.api.annotations.ParameterObject");
36823681
assertFileContains(petApi.toPath(), "pageable: Pageable");
36833682
assertFileContains(petApi.toPath(), "@Parameter(hidden = true) pageable: Pageable");
36843683
}
@@ -3696,7 +3695,6 @@ public void springPaginatedWithSpringDocAndSpringBoot3() throws Exception {
36963695

36973696
File petApi = files.get("PetApi.kt");
36983697
assertFileContains(petApi.toPath(), "import org.springframework.data.domain.Pageable");
3699-
assertFileContains(petApi.toPath(), "import org.springdoc.core.annotations.ParameterObject");
37003698
assertFileContains(petApi.toPath(), "pageable: Pageable");
37013699
}
37023700

@@ -3862,6 +3860,49 @@ public void springPaginatedWithSwagger1AnnotationLibrary() throws Exception {
38623860
assertFileContains(petApi.toPath(), "@ApiParam(hidden = true) pageable: Pageable");
38633861
}
38643862

3863+
@Test
3864+
public void springPaginatedWithSpringDocUsesPageableAsQueryParam() throws Exception {
3865+
Map<String, Object> additionalProperties = new HashMap<>();
3866+
additionalProperties.put(USE_TAGS, "true");
3867+
additionalProperties.put(DOCUMENTATION_PROVIDER, "springdoc");
3868+
additionalProperties.put(INTERFACE_ONLY, "true");
3869+
additionalProperties.put(SKIP_DEFAULT_INTERFACE, "true");
3870+
3871+
Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/petstore-with-spring-pageable.yaml", additionalProperties);
3872+
3873+
File petApi = files.get("PetApi.kt");
3874+
String content = Files.readString(petApi.toPath());
3875+
3876+
// Verify @PageableAsQueryParam annotation is present at method level
3877+
assertFileContains(petApi.toPath(), "import org.springdoc.core.converters.models.PageableAsQueryParam");
3878+
assertFileContains(petApi.toPath(), "@PageableAsQueryParam");
3879+
3880+
// Verify Pageable parameter has @Parameter(hidden = true)
3881+
assertFileContains(petApi.toPath(), "@Parameter(hidden = true) pageable: Pageable");
3882+
3883+
// Verify the annotation appears before @RequestMapping for findPetsByStatus
3884+
int findPetsByStatusStart = content.indexOf("fun findPetsByStatus(");
3885+
Assert.assertTrue(findPetsByStatusStart > 0, "findPetsByStatus method should exist");
3886+
3887+
String methodBlock = content.substring(Math.max(0, findPetsByStatusStart - 1000), findPetsByStatusStart);
3888+
int pageableAsQueryParamPos = methodBlock.lastIndexOf("@PageableAsQueryParam");
3889+
int requestMappingPos = methodBlock.lastIndexOf("@RequestMapping");
3890+
3891+
Assert.assertTrue(pageableAsQueryParamPos > 0, "@PageableAsQueryParam should be present before method");
3892+
Assert.assertTrue(requestMappingPos > pageableAsQueryParamPos,
3893+
"@PageableAsQueryParam should appear before @RequestMapping");
3894+
3895+
// Verify page, size, sort parameters are NOT in the method signature
3896+
String methodSignature = content.substring(findPetsByStatusStart,
3897+
content.indexOf("): ResponseEntity", findPetsByStatusStart));
3898+
Assert.assertFalse(methodSignature.contains("page:"),
3899+
"page parameter should be removed from method signature");
3900+
Assert.assertFalse(methodSignature.contains("size:") && methodSignature.contains("@RequestParam"),
3901+
"size query parameter should be removed from method signature");
3902+
Assert.assertFalse(methodSignature.contains("sort:"),
3903+
"sort parameter should be removed from method signature");
3904+
}
3905+
38653906
@Test
38663907
public void springPaginatedNoParamsNoContext() throws Exception {
38673908
Map<String, Object> additionalProperties = new HashMap<>();

0 commit comments

Comments
 (0)