diff --git a/modules/openapi-generator/pom.xml b/modules/openapi-generator/pom.xml index fb37ae9eafb9..8f9990c7c7ed 100644 --- a/modules/openapi-generator/pom.xml +++ b/modules/openapi-generator/pom.xml @@ -385,7 +385,6 @@ com.github.javaparser javaparser-core 3.24.9 - test com.googlecode.java-diff-utils diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 2104047f125c..c822defb7c1e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -17,6 +17,13 @@ package org.openapitools.codegen.languages; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.Type; import com.samskivert.mustache.Mustache; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; @@ -50,7 +57,6 @@ import java.net.URL; import java.util.*; import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isNotEmpty; @@ -1279,9 +1285,15 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation importMapping.put("Pageable", "org.springframework.data.domain.Pageable"); } - Set provideArgsClassSet = reformatProvideArgsParams(operation); + ProvideArgsParams provideArgsParams = reformatProvideArgsParams(operation); CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, servers); + if (!provideArgsParams.names.isEmpty()) { + codegenOperation.vendorExtensions.put("springProvideArgsNames", provideArgsParams.names); + } + if (!provideArgsParams.delegateArgs.isEmpty()) { + codegenOperation.vendorExtensions.put("springProvideArgsDelegate", provideArgsParams.delegateArgs); + } // add org.springframework.format.annotation.DateTimeFormat when needed codegenOperation.allParams.stream().filter(p -> p.isDate || p.isDateTime).findFirst() @@ -1310,8 +1322,8 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation generatePageableConstraintValidation, useBeanValidation, generateSortValidation, SpringPageableScanUtils.AnnotationSyntax.JAVA); } - if (codegenOperation.vendorExtensions.containsKey("x-spring-provide-args") && !provideArgsClassSet.isEmpty()) { - codegenOperation.imports.addAll(provideArgsClassSet); + if (codegenOperation.vendorExtensions.containsKey("x-spring-provide-args") && !provideArgsParams.imports.isEmpty()) { + codegenOperation.imports.addAll(provideArgsParams.imports); } if (isSpringCodegen()) { @@ -1395,41 +1407,82 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation return codegenOperation; } - private Set reformatProvideArgsParams(Operation operation) { - Set provideArgsClassSet = new HashSet<>(); + private ProvideArgsParams reformatProvideArgsParams(Operation operation) { + ProvideArgsParams provideArgsParams = new ProvideArgsParams(); Object argObj = operation.getExtensions().get("x-spring-provide-args"); if (argObj instanceof List) { List provideArgs = (List) argObj; if (!provideArgs.isEmpty()) { List formattedArgs = new ArrayList<>(); + List formattedArgNames = new ArrayList<>(); + List formattedDelegateArgs = new ArrayList<>(); for (String oneArg : provideArgs) { if (StringUtils.isNotEmpty(oneArg)) { - String regexp = "(?@)?(?(?(\\w+\\.)*)(?\\w+))(?\\(.*?\\))?\\s?"; - Matcher matcher = Pattern.compile(regexp).matcher(oneArg); - List newArgs = new ArrayList<>(); - while (matcher.find()) { - String className = matcher.group("ClassName"); - String classPath = matcher.group("ClassPath"); - String packageName = matcher.group("PackageName"); - String params = matcher.group("Params"); - String annoTag = matcher.group("AnnotationTag"); - String shortPhrase = StringUtils.join(annoTag, className, params); - newArgs.add(shortPhrase); - if (StringUtils.isNotEmpty(packageName)) { - importMapping.put(className, classPath); - provideArgsClassSet.add(className); - LOGGER.trace("put import mapping {} {}", className, classPath); - } - } - String newArg = String.join(" ", newArgs); + Parameter parameter = parseProvideArgParameter(oneArg); + collectImportsAndSimplify(parameter, provideArgsParams); + + String newArg = parameter.toString(); LOGGER.trace("new arg {} {}", newArg); formattedArgs.add(newArg); + + Parameter delegateParameter = parameter.clone(); + delegateParameter.getAnnotations().clear(); + formattedDelegateArgs.add(delegateParameter.toString()); + formattedArgNames.add(parameter.getNameAsString()); } } operation.getExtensions().put("x-spring-provide-args", formattedArgs); + provideArgsParams.names.addAll(formattedArgNames); + provideArgsParams.delegateArgs.addAll(formattedDelegateArgs); + } + } + return provideArgsParams; + } + + private Parameter parseProvideArgParameter(String oneArg) { + CompilationUnit compilationUnit = StaticJavaParser.parse(String.format(Locale.ROOT, "class Dummy { void method(%s) {} }", oneArg)); + return compilationUnit.findFirst(MethodDeclaration.class) + .orElseThrow(() -> new IllegalArgumentException("Unable to parse x-spring-provide-args parameter: " + oneArg)) + .getParameter(0); + } + + private void collectImportsAndSimplify(Parameter parameter, ProvideArgsParams provideArgsParams) { + parameter.findAll(AnnotationExpr.class).forEach(annotation -> { + String annotationName = annotation.getNameAsString(); + if (annotationName.contains(".")) { + String simpleName = annotation.getName().getIdentifier(); + importMapping.put(simpleName, annotationName); + provideArgsParams.imports.add(simpleName); + annotation.setName(simpleName); + LOGGER.trace("put import mapping {} {}", simpleName, annotationName); + } + }); + + parameter.getType().toClassOrInterfaceType().ifPresent(type -> simplifyClassOrInterfaceType(type, provideArgsParams)); + } + + private void simplifyClassOrInterfaceType(ClassOrInterfaceType type, ProvideArgsParams provideArgsParams) { + type.getTypeArguments().stream() + .flatMap(Collection::stream) + .map(Type::toClassOrInterfaceType) + .flatMap(Optional::stream) + .forEach(classOrInterfaceType -> simplifyClassOrInterfaceType(classOrInterfaceType, provideArgsParams)); + if (type.getScope().isPresent()) { + String typeName = type.getNameWithScope(); + if (typeName.contains(".")) { + String simpleName = type.getNameAsString(); + importMapping.put(simpleName, typeName); + provideArgsParams.imports.add(simpleName); + type.setScope(null); + LOGGER.trace("put import mapping {} {}", simpleName, typeName); } } - return provideArgsClassSet; + } + + private static final class ProvideArgsParams { + private final Set imports = new HashSet<>(); + private final List names = new ArrayList<>(); + private final List delegateArgs = new ArrayList<>(); } @Override diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache index 5ac4a949ca1d..bcdae723588d 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache @@ -279,22 +279,22 @@ public interface {{classname}} { {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}} final {{#reactive}}ServerWebExchange exchange{{/reactive}}{{^reactive}}HttpServletRequest servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, - {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},{{/includeHttpRequestContext}}{{/hasParams}}{{#vendorExtensions.x-pageable-extra-annotation}}{{{.}}} {{/vendorExtensions.x-pageable-extra-annotation}}{{#springDocDocumentationProvider}}@ParameterObject{{/springDocDocumentationProvider}} final Pageable pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.x-spring-provide-args}}{{#hasParams}}, - {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},{{/includeHttpRequestContext}}{{/hasParams}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}} {{{.}}}{{^hasParams}}{{^-last}}{{^reactive}},{{/reactive}} - {{/-last}}{{/hasParams}}{{/vendorExtensions.x-spring-provide-args}} + {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},{{/includeHttpRequestContext}}{{/hasParams}}{{#vendorExtensions.x-pageable-extra-annotation}}{{{.}}} {{/vendorExtensions.x-pageable-extra-annotation}}{{#springDocDocumentationProvider}}@ParameterObject{{/springDocDocumentationProvider}} final Pageable pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.x-spring-provide-args}}{{#-first}}{{#hasParams}}, + {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},{{/includeHttpRequestContext}}{{^includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}},{{/vendorExtensions.x-spring-paginated}}{{/includeHttpRequestContext}}{{/hasParams}}{{/-first}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}} {{{.}}}{{^-last}}, + {{/-last}}{{/vendorExtensions.x-spring-provide-args}} ){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { {{#delegate-method}} - {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}); + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.springProvideArgsNames}}{{#-first}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{^includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}, {{/vendorExtensions.x-spring-paginated}}{{/includeHttpRequestContext}}{{/hasParams}}{{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.springProvideArgsNames}}); } // Override this method - {{#jdk8-default-interface}}default {{/jdk8-default-interface}} {{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}} final {{#reactive}}ServerWebExchange exchange{{/reactive}}{{^reactive}}HttpServletRequest servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}} { + {{#jdk8-default-interface}}default {{/jdk8-default-interface}} {{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}} final {{#reactive}}ServerWebExchange exchange{{/reactive}}{{^reactive}}HttpServletRequest servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.springProvideArgsDelegate}}{{#-first}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{^includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}, {{/vendorExtensions.x-spring-paginated}}{{/includeHttpRequestContext}}{{/hasParams}}{{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.springProvideArgsDelegate}}){{#unhandledException}} throws Exception{{/unhandledException}} { {{/delegate-method}} {{^isDelegate}} {{>methodBody}}{{! prevent indent}} {{/isDelegate}} {{#isDelegate}} - {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}getDelegate().{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}); + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}getDelegate().{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.springProvideArgsNames}}{{#-first}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{^includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}, {{/vendorExtensions.x-spring-paginated}}{{/includeHttpRequestContext}}{{/hasParams}}{{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.springProvideArgsNames}}); {{/isDelegate}} }{{/jdk8-default-interface}} diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/apiController.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/apiController.mustache index 7de0ac763bcf..17ea2c148d5e 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/apiController.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/apiController.mustache @@ -123,7 +123,10 @@ public class {{classname}}Controller implements {{classname}} { public {{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}( {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, - {{/hasParams}}{{^hasParams}}{{#reactive}},{{/reactive}}{{/hasParams}}{{#springDocDocumentationProvider}}@ParameterObject {{/springDocDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}} + {{/hasParams}}{{^hasParams}}{{#reactive}},{{/reactive}}{{/hasParams}}{{#springDocDocumentationProvider}}@ParameterObject {{/springDocDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.x-spring-provide-args}}{{#-first}}{{#hasParams}}, + {{/hasParams}}{{^hasParams}}{{#vendorExtensions.x-spring-paginated}}, + {{/vendorExtensions.x-spring-paginated}}{{/hasParams}}{{/-first}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}} {{{.}}}{{^-last}}, + {{/-last}}{{/vendorExtensions.x-spring-provide-args}} ) { {{^isDelegate}} {{^async}} @@ -139,7 +142,7 @@ public class {{classname}}Controller implements {{classname}} { {{/async}} {{/isDelegate}} {{#isDelegate}} - {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}delegate.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}); + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}delegate.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.springProvideArgsNames}}{{#-first}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#vendorExtensions.x-spring-paginated}}, {{/vendorExtensions.x-spring-paginated}}{{/hasParams}}{{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.springProvideArgsNames}}); {{/isDelegate}} } diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/apiDelegate.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/apiDelegate.mustache index 12ed158dedb5..20a9560d2dce 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/apiDelegate.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/apiDelegate.mustache @@ -79,7 +79,7 @@ public interface {{classname}}Delegate { {{/isDeprecated}} {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}{{#isFormParam}}MultipartFile{{/isFormParam}}{{^isFormParam}}{{>optionalDataType}}{{/isFormParam}}{{#isArray}}>{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, - {{/hasParams}}{{#reactive}}ServerWebExchange exchange{{/reactive}}{{^reactive}}HttpServletRequest servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { + {{/hasParams}}{{#reactive}}ServerWebExchange exchange{{/reactive}}{{^reactive}}HttpServletRequest servletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.springProvideArgsDelegate}}{{#-first}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{^includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}, {{/vendorExtensions.x-spring-paginated}}{{/includeHttpRequestContext}}{{/hasParams}}{{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.springProvideArgsDelegate}}){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { {{>methodBody}}{{! prevent indent}} }{{/jdk8-default-interface}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index e7a4f02f7f0b..9a8377309121 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -38,6 +38,8 @@ import org.openapitools.codegen.languages.features.BeanValidationFeatures; import org.openapitools.codegen.languages.features.CXFServerFeatures; import org.openapitools.codegen.languages.features.DocumentationProviderFeatures; +import org.openapitools.codegen.model.ModelMap; +import org.openapitools.codegen.model.OperationsMap; import org.openapitools.codegen.testutils.ConfigAssert; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -8147,6 +8149,155 @@ void schemaMappingWithNullableAllOfRendersNullableJavaProperty() throws IOExcept } @Test + public void shouldPassXSpringProvideArgsToOverridableMethodWithApiInterfaceRequestMapping() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec( + "src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml"); + final SpringCodegen codegen = new SpringCodegen(); + codegen.setOpenAPI(openAPI); + codegen.setLibrary(SPRING_BOOT); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(INTERFACE_ONLY, "true"); + codegen.additionalProperties().put(DELEGATE_PATTERN, "true"); + codegen.additionalProperties().put(REQUEST_MAPPING_OPTION, SpringCodegen.RequestMappingMode.api_interface.name()); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGenerateMetadata(false); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + + generator.opts(input).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/api/FooApi.java")) + .fileContains("default ResponseEntity _foo(") + .fileContains("@Parameter(hidden = true) @Size(max = 64) String providedArg") + .fileContains("return foo(providedArg);") + .fileContains("default ResponseEntity foo(String providedArg)"); + } + + @Test + public void shouldIncludeXSpringProvideArgsInDelegateWithApiInterfaceRequestMapping() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec( + "src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml"); + final SpringCodegen codegen = new SpringCodegen(); + codegen.setOpenAPI(openAPI); + codegen.setLibrary(SPRING_BOOT); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(DELEGATE_PATTERN, "true"); + codegen.additionalProperties().put(REQUEST_MAPPING_OPTION, SpringCodegen.RequestMappingMode.api_interface.name()); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGenerateMetadata(false); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + + generator.opts(input).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/api/FooApi.java")) + .fileContains("@Parameter(hidden = true) @Size(max = 64) String providedArg") + .fileContains("return getDelegate().foo(providedArg);"); + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/api/FooApiDelegate.java")) + .fileContains("default ResponseEntity foo(String providedArg)"); + } + + @Test + public void shouldPassXSpringProvideArgsFromControllerToDelegate() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec( + "src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml"); + final SpringCodegen codegen = new SpringCodegen() { + @Override + public void processOpts() { + super.processOpts(); + additionalProperties().put("_api_controller_impl_", true); + } + + @Override + public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) { + OperationsMap operations = super.postProcessOperationsWithModels(objs, allModels); + operations.put("_api_controller_impl_", true); + return operations; + } + }; + codegen.setOpenAPI(openAPI); + codegen.setLibrary(SPRING_BOOT); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(DELEGATE_PATTERN, "true"); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGenerateMetadata(false); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + + generator.opts(input).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/api/FooApiController.java")) + .fileContains("@Parameter(hidden = true) @Size(max = 64) String providedArg") + .fileContains("return delegate.foo(providedArg);"); + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/api/FooApiDelegate.java")) + .fileContains("default ResponseEntity foo(String providedArg)"); + } + + @Test + public void shouldIncludeXSpringProvideArgsWithInterfaceOnlyWithoutDelegatePattern() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec( + "src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml"); + final SpringCodegen codegen = new SpringCodegen(); + codegen.setOpenAPI(openAPI); + codegen.setLibrary(SPRING_BOOT); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(INTERFACE_ONLY, "true"); + codegen.additionalProperties().put(DELEGATE_PATTERN, "false"); + codegen.additionalProperties().put(REQUEST_MAPPING_OPTION, SpringCodegen.RequestMappingMode.api_interface.name()); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGenerateMetadata(false); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + + generator.opts(input).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/api/FooApi.java")) + .fileContains("default ResponseEntity foo(") + .fileContains("@Parameter(hidden = true) @Size(max = 64) String providedArg") + .fileDoesNotContain("default ResponseEntity _foo(") + .fileDoesNotContain("return foo(providedArg);"); + } + void issue24003() throws IOException { Map files = generateFromContract( "src/test/resources/3_0/spring/issue_24003.yaml", SPRING_BOOT, @@ -8162,4 +8313,4 @@ void issue24003() throws IOException { JavaFileAssert.assertThat(files.get("UserBrLockDTO.java")).implementsInterfaces("BrLockDTO") .fileDoesNotContain("@JsonTypeName"); } -} \ No newline at end of file +} diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml b/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml new file mode 100644 index 000000000000..c59210f90e70 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml @@ -0,0 +1,15 @@ +openapi: 3.0.3 +info: + title: x-spring-provide-args api interface test + version: 1.0.0 +paths: + /foo: + get: + tags: + - foo + operationId: foo + x-spring-provide-args: + - "@jakarta.validation.constraints.Size(max = 64) String providedArg" + responses: + "204": + description: no content