From b85a6a12676af446b8d89fce0b968012e5f91029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Haisman?= Date: Thu, 18 Jun 2026 20:30:58 +0200 Subject: [PATCH] Fix x-spring-provide-args handling. Pass arguments to delegates. Use JavaParser. --- modules/openapi-generator/pom.xml | 1 - .../codegen/languages/SpringCodegen.java | 103 ++++-- .../main/resources/JavaSpring/api.mustache | 12 +- .../JavaSpring/apiController.mustache | 7 +- .../resources/JavaSpring/apiDelegate.mustache | 2 +- .../java/spring/SpringCodegenTest.java | 306 +++++++++++++++++- ...-spring-no-provide-args-api-interface.yaml | 13 + .../x-spring-provide-args-api-interface.yaml | 15 + .../x-spring-provide-args-pageable.yaml | 16 + .../java/org/openapitools/api/UserApi.java | 4 +- .../src/main/resources/openapi.yaml | 4 +- 11 files changed, 443 insertions(+), 40 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/spring/x-spring-no-provide-args-api-interface.yaml create mode 100644 modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml create mode 100644 modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-pageable.yaml 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..c7fc82cb4106 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}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/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}}{{#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}} { {{/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}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/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..b5e42a1d19d6 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}}{{#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}}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..da5d5afe48fc 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; @@ -8146,6 +8148,308 @@ void schemaMappingWithNullableAllOfRendersNullableJavaProperty() throws IOExcept .assertProperty("optionalRef").withType("JsonNullable"); } + @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);"); + } + + @DataProvider(name = "reactiveWithoutPageableWithAndWithoutProvidedArgs") + public Object[][] reactiveWithoutPageableWithAndWithoutProvidedArgs() { + return new Object[][]{ + {"src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml", true, true, + "return getDelegate().foo(exchange, providedArg);", + "default Mono> foo(ServerWebExchange exchange, String providedArg)"}, + {"src/test/resources/3_0/spring/x-spring-no-provide-args-api-interface.yaml", false, true, + "return getDelegate().foo(exchange);", + "default Mono> foo(ServerWebExchange exchange)"}, + {"src/test/resources/3_0/spring/x-spring-provide-args-api-interface.yaml", true, false, + "return getDelegate().foo(providedArg);", + "default Mono> foo(String providedArg)"}, + {"src/test/resources/3_0/spring/x-spring-no-provide-args-api-interface.yaml", false, false, + "return getDelegate().foo();", + "default Mono> foo()"} + }; + } + + @Test(dataProvider = "reactiveWithoutPageableWithAndWithoutProvidedArgs") + public void shouldGenerateReactiveWithoutPageableWithAndWithoutXSpringProvidedArgs( + String spec, + boolean hasProvidedArgs, + boolean includeHttpRequestContext, + String expectedDelegateCall, + String expectedApiDelegateMethodSignature) throws IOException { + Map files = generateFromContract( + spec, + SPRING_BOOT, + Map.of(DELEGATE_PATTERN, "true", + REACTIVE, "true", + INCLUDE_HTTP_REQUEST_CONTEXT, Boolean.toString(includeHttpRequestContext))); + + JavaFileAssert.assertThat(files.get("FooApi.java")) + .fileContains(expectedDelegateCall) + .fileDoesNotContain("foo(,", "exchangenull", "exchangeprovidedArg", "exchange, );"); + JavaFileAssert.assertThat(files.get("FooApiDelegate.java")) + .fileContains(expectedApiDelegateMethodSignature) + .fileDoesNotContain("foo(,", "exchangeprovidedArg"); + if (hasProvidedArgs) { + JavaFileAssert.assertThat(files.get("FooApi.java")) + .fileContains("@Parameter(hidden = true) @Size(max = 64) String providedArg"); + } else { + JavaFileAssert.assertThat(files.get("FooApi.java")) + .fileDoesNotContain("providedArg"); + } + } + + @DataProvider(name = "requestContextReactiveAndDelegateArgs") + public Object[][] requestContextReactiveAndDelegateArgs() { + return new Object[][]{ + {false, false, "return getDelegate().findFoo(pageable, providedArg);"}, + {false, true, "return getDelegate().findFoo(servletRequest, pageable, providedArg);"}, + {true, false, "return getDelegate().findFoo(pageable, providedArg);"}, + {true, true, "return getDelegate().findFoo(exchange, pageable, providedArg);"} + }; + } + + @Test(dataProvider = "requestContextReactiveAndDelegateArgs") + public void shouldSeparatePageableAndXSpringProvideArgsForRequestContextAndReactiveCombinations( + boolean reactive, + boolean includeHttpRequestContext, + String expectedDelegateCall) throws IOException { + Map files = generateFromContract( + "src/test/resources/3_0/spring/x-spring-provide-args-pageable.yaml", + SPRING_BOOT, + Map.of(DELEGATE_PATTERN, "true", + REACTIVE, Boolean.toString(reactive), + INCLUDE_HTTP_REQUEST_CONTEXT, Boolean.toString(includeHttpRequestContext))); + + JavaFileAssert.assertThat(files.get("FooApi.java")) + .fileContains(expectedDelegateCall) + .fileDoesNotContain("findFoo(,", "servletRequestpageable", "exchangepageable"); + JavaFileAssert.assertThat(files.get("FooApiDelegate.java")) + .fileContains(expectedApiDelegateMethodSignature(reactive, includeHttpRequestContext)) + .fileDoesNotContain("findFoo(,", "servletRequestpageable", "exchangepageable"); + } + + private String expectedApiDelegateMethodSignature(boolean reactive, boolean includeHttpRequestContext) { + String returnType = reactive ? "Mono>" : "ResponseEntity"; + String contextParameter = ""; + if (includeHttpRequestContext) { + contextParameter = reactive ? "ServerWebExchange exchange, " : "HttpServletRequest servletRequest, "; + } + return "default " + returnType + " findFoo(" + contextParameter + "final Pageable pageable, String providedArg)"; + } + + @Test(dataProvider = "requestContextReactiveAndDelegateArgs") + public void shouldSeparatePageableAndXSpringProvideArgsForApiInterfaceDelegateMethodCombinations( + boolean reactive, + boolean includeHttpRequestContext, + String expectedDelegateCall) throws IOException { + String expectedOverrideCall = expectedDelegateCall.replace("getDelegate().findFoo", "findFoo"); + Map files = generateFromContract( + "src/test/resources/3_0/spring/x-spring-provide-args-pageable.yaml", + SPRING_BOOT, + Map.of(INTERFACE_ONLY, "true", + DELEGATE_PATTERN, "true", + REQUEST_MAPPING_OPTION, SpringCodegen.RequestMappingMode.api_interface.name(), + REACTIVE, Boolean.toString(reactive), + INCLUDE_HTTP_REQUEST_CONTEXT, Boolean.toString(includeHttpRequestContext))); + + JavaFileAssert.assertThat(files.get("FooApi.java")) + .fileContains(expectedOverrideCall) + .fileDoesNotContain("findFoo(,", "servletRequestpageable", "exchangepageable"); + } + + @Test + public void shouldSeparatePageableAndXSpringProvideArgsInReactiveControllerImplementation() 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-pageable.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"); + codegen.additionalProperties().put(REACTIVE, "true"); + codegen.additionalProperties().put(USE_RESPONSE_ENTITY, "false"); + + 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("return delegate.findFoo(pageable, providedArg);") + .fileDoesNotContain("findFoo(,", "delegate.findFoo(,"); + } + @Test void issue24003() throws IOException { Map files = generateFromContract( @@ -8162,4 +8466,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-no-provide-args-api-interface.yaml b/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-no-provide-args-api-interface.yaml new file mode 100644 index 000000000000..4819faca2777 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-no-provide-args-api-interface.yaml @@ -0,0 +1,13 @@ +openapi: 3.0.3 +info: + title: no x-spring-provide-args api interface test + version: 1.0.0 +paths: + /foo: + get: + tags: + - foo + operationId: foo + responses: + "204": + description: no content 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 diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-pageable.yaml b/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-pageable.yaml new file mode 100644 index 000000000000..d2f5497b2b49 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/spring/x-spring-provide-args-pageable.yaml @@ -0,0 +1,16 @@ +openapi: 3.0.3 +info: + title: x-spring-provide-args pageable test + version: 1.0.0 +paths: + /foo: + get: + tags: + - foo + operationId: findFoo + x-spring-paginated: true + x-spring-provide-args: + - "String providedArg" + responses: + "204": + description: no content diff --git a/samples/server/petstore/springboot-spring-provide-args/src/main/java/org/openapitools/api/UserApi.java b/samples/server/petstore/springboot-spring-provide-args/src/main/java/org/openapitools/api/UserApi.java index 0567ae2d8204..37451e7a940a 100644 --- a/samples/server/petstore/springboot-spring-provide-args/src/main/java/org/openapitools/api/UserApi.java +++ b/samples/server/petstore/springboot-spring-provide-args/src/main/java/org/openapitools/api/UserApi.java @@ -76,7 +76,7 @@ default ResponseEntity loginUser( @NotNull @Parameter(name = "username", description = "The user name for login", required = true, in = ParameterIn.QUERY) @Valid @RequestParam(value = "username", required = true) String username, @NotNull @Parameter(name = "password", description = "The password for login in clear text", required = true, in = ParameterIn.QUERY) @Valid @RequestParam(value = "password", required = true) String password, @Parameter(hidden = true) @Value("${server.port}") String someValue, - @Parameter(hidden = true) @RequestHeader(value="x-project-id", required = false) String someHeaderValue, + @Parameter(hidden = true) @RequestHeader(value = "x-project-id", required = false) String someHeaderValue, @Parameter(hidden = true) @RequestHeader final HttpHeaders headers, @Parameter(hidden = true) Principal principal, @Parameter(hidden = true) @Qualifier("jacksonObjectMapper") ObjectMapper mapper @@ -106,7 +106,7 @@ default ResponseEntity loginUser( ) default ResponseEntity logoutUser( @Parameter(hidden = true) @Value("${server.port}") String somePropertyValue, - @Parameter(hidden = true) @RequestHeader(value="x-project-id", required = false) String someHeaderValue, + @Parameter(hidden = true) @RequestHeader(value = "x-project-id", required = false) String someHeaderValue, @Parameter(hidden = true) @RequestHeader final HttpHeaders headers, @Parameter(hidden = true) Principal principal, @Parameter(hidden = true) @Qualifier("jacksonObjectMapper") ObjectMapper mapper diff --git a/samples/server/petstore/springboot-spring-provide-args/src/main/resources/openapi.yaml b/samples/server/petstore/springboot-spring-provide-args/src/main/resources/openapi.yaml index 8b598997cf97..d9f9f101e735 100644 --- a/samples/server/petstore/springboot-spring-provide-args/src/main/resources/openapi.yaml +++ b/samples/server/petstore/springboot-spring-provide-args/src/main/resources/openapi.yaml @@ -74,7 +74,7 @@ paths: - user x-spring-provide-args: - "@Value(\"${server.port}\") String someValue" - - "@RequestHeader(value=\"x-project-id\", required = false) String someHeaderValue" + - "@RequestHeader(value = \"x-project-id\", required = false) String someHeaderValue" - '@RequestHeader final HttpHeaders headers' - Principal principal - '@Qualifier("jacksonObjectMapper") ObjectMapper mapper' @@ -94,7 +94,7 @@ paths: - user x-spring-provide-args: - "@Value(\"${server.port}\") String somePropertyValue" - - "@RequestHeader(value=\"x-project-id\", required = false) String someHeaderValue" + - "@RequestHeader(value = \"x-project-id\", required = false) String someHeaderValue" - '@RequestHeader final HttpHeaders headers' - Principal principal - '@Qualifier("jacksonObjectMapper") ObjectMapper mapper'