Skip to content

Commit f0e587e

Browse files
committed
add x-kotlin-implements
1 parent a53ebb3 commit f0e587e

6 files changed

Lines changed: 237 additions & 12 deletions

File tree

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

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,12 @@ public void processOpts() {
447447
additionalProperties.put(DOCUMENTATION_PROVIDER, DocumentationProvider.NONE);
448448
additionalProperties.put(ANNOTATION_LIBRARY, AnnotationLibrary.NONE);
449449
}
450+
if (additionalProperties.containsKey(USE_SPRING_BOOT3)) {
451+
this.setUseSpringBoot3(convertPropertyToBoolean(USE_SPRING_BOOT3));
452+
}
453+
if (additionalProperties.containsKey(INCLUDE_HTTP_REQUEST_CONTEXT)) {
454+
this.setIncludeHttpRequestContext(convertPropertyToBoolean(INCLUDE_HTTP_REQUEST_CONTEXT));
455+
}
450456

451457
if (isModelMutable()) {
452458
typeMapping.put("array", "kotlin.collections.MutableList");
@@ -470,6 +476,13 @@ public void processOpts() {
470476
// used later in recursive import in postProcessingModels
471477
importMapping.put("com.fasterxml.jackson.annotation.JsonProperty", "com.fasterxml.jackson.annotation.JsonCreator");
472478

479+
// Spring-specific import mappings for x-spring-paginated support
480+
importMapping.put("ApiIgnore", "springfox.documentation.annotations.ApiIgnore");
481+
importMapping.put("ParameterObject", "org.springdoc.api.annotations.ParameterObject");
482+
if (useSpringBoot3) {
483+
importMapping.put("ParameterObject", "org.springdoc.core.annotations.ParameterObject");
484+
}
485+
473486
if (!additionalProperties.containsKey(CodegenConstants.LIBRARY)) {
474487
additionalProperties.put(CodegenConstants.LIBRARY, library);
475488
}
@@ -642,13 +655,6 @@ public void processOpts() {
642655
if (additionalProperties.containsKey(USE_TAGS)) {
643656
this.setUseTags(Boolean.parseBoolean(additionalProperties.get(USE_TAGS).toString()));
644657
}
645-
646-
if (additionalProperties.containsKey(USE_SPRING_BOOT3)) {
647-
this.setUseSpringBoot3(convertPropertyToBoolean(USE_SPRING_BOOT3));
648-
}
649-
if (additionalProperties.containsKey(INCLUDE_HTTP_REQUEST_CONTEXT)) {
650-
this.setIncludeHttpRequestContext(convertPropertyToBoolean(INCLUDE_HTTP_REQUEST_CONTEXT));
651-
}
652658
if (isUseSpringBoot3()) {
653659
if (DocumentationProvider.SPRINGFOX.equals(getDocumentationProvider())) {
654660
throw new IllegalArgumentException(DocumentationProvider.SPRINGFOX.getPropertyName() + " is not supported with Spring Boot > 3.x");
@@ -872,6 +878,60 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
872878
}
873879
}
874880

881+
/**
882+
* Processes operations to support the x-spring-paginated vendor extension.
883+
*
884+
* When x-spring-paginated is set to true on an operation, this method:
885+
* - Adds org.springframework.data.domain.Pageable parameter to the method signature
886+
* - Removes the default Spring Data Web pagination query parameters (page, size, sort)
887+
* - Adds appropriate imports (Pageable, ApiIgnore for springfox, ParameterObject for springdoc)
888+
*
889+
* Parameter ordering in generated methods:
890+
* 1. Regular OpenAPI parameters (allParams)
891+
* 2. Optional HttpServletRequest/ServerWebExchange (if includeHttpRequestContext is enabled)
892+
* 3. Pageable parameter (if x-spring-paginated is true)
893+
*
894+
* This implementation mirrors the behavior in SpringCodegen for consistency.
895+
*
896+
* @param path the operation path
897+
* @param httpMethod the HTTP method
898+
* @param operation the OpenAPI operation
899+
* @param servers the list of servers
900+
* @return the processed CodegenOperation
901+
*/
902+
@Override
903+
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List<io.swagger.v3.oas.models.servers.Server> servers) {
904+
// add Pageable import only if x-spring-paginated explicitly used
905+
// this allows to use a custom Pageable schema without importing Spring Pageable.
906+
if (operation.getExtensions() != null && Boolean.TRUE.equals(operation.getExtensions().get("x-spring-paginated"))) {
907+
importMapping.putIfAbsent("Pageable", "org.springframework.data.domain.Pageable");
908+
}
909+
910+
CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, servers);
911+
912+
// add org.springframework.data.domain.Pageable import when needed
913+
if (codegenOperation.vendorExtensions.containsKey("x-spring-paginated")) {
914+
codegenOperation.imports.add("Pageable");
915+
if (DocumentationProvider.SPRINGFOX.equals(getDocumentationProvider())) {
916+
codegenOperation.imports.add("ApiIgnore");
917+
}
918+
if (DocumentationProvider.SPRINGDOC.equals(getDocumentationProvider())) {
919+
codegenOperation.imports.add("ParameterObject");
920+
}
921+
922+
// #8315 Spring Data Web default query params recognized by Pageable
923+
List<String> defaultPageableQueryParams = new ArrayList<>(
924+
Arrays.asList("page", "size", "sort")
925+
);
926+
927+
// #8315 Remove matching Spring Data Web default query params if 'x-spring-paginated' with Pageable is used
928+
codegenOperation.queryParams.removeIf(param -> defaultPageableQueryParams.contains(param.baseName));
929+
codegenOperation.allParams.removeIf(param -> param.isQueryParam && defaultPageableQueryParams.contains(param.baseName));
930+
}
931+
932+
return codegenOperation;
933+
}
934+
875935
@Override
876936
public void preprocessOpenAPI(OpenAPI openAPI) {
877937
super.preprocessOpenAPI(openAPI);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ class {{classname}}Controller({{#serviceInterface}}@Autowired(required = true) v
9595
)
9696
{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}
9797
{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>cookieParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}},
98-
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#hasParams}}
98+
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
99+
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
100+
{{/includeHttpRequestContext}}{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}{{#hasParams}}
99101
{{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} {
100102
return {{>returnValue}}
101103
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ interface {{classname}}Delegate {
3434
*/
3535
{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}{{#isBodyParam}}Flow<{{{baseType}}}>{{/isBodyParam}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{/isArray}}{{/reactive}}{{^-last}},
3636
{{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}},
37-
{{/hasParams}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultDelegateInterface}} {
37+
{{/hasParams}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
38+
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
39+
{{/includeHttpRequestContext}}{{/hasParams}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultDelegateInterface}} {
3840
{{>methodBody}}{{! prevent indent}}
3941
}{{/skipDefaultDelegateInterface}}
4042

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,15 @@ interface {{classname}} {
110110
)
111111
{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}
112112
{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>cookieParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}},
113-
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#hasParams}}
113+
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
114+
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
115+
{{/includeHttpRequestContext}}{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}{{#hasParams}}
114116
{{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultApiInterface}} {
115117
{{^isDelegate}}
116118
return {{>returnValue}}
117119
{{/isDelegate}}
118120
{{#isDelegate}}
119-
return getDelegate().{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}})
121+
return getDelegate().{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}})
120122
{{/isDelegate}}
121123
}{{/skipDefaultApiInterface}}
122124

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class {{classname}}Test {
3535
val {{{paramName}}}: {{>optionalDataType}} = TODO()
3636
{{/allParams}}
3737
{{#includeHttpRequestContext}}val {{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}} = TODO(){{/includeHttpRequestContext}}
38-
val response: {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} = api.{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}})
38+
{{#vendorExtensions.x-spring-paginated}}val pageable: Pageable = TODO(){{/vendorExtensions.x-spring-paginated}}
39+
val response: {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} = api.{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}})
3940

4041
// TODO: test validations
4142
}

0 commit comments

Comments
 (0)