mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-10-14 00:13:50 +00:00
* Add spring api versioning support * Do not add version if not requested * Fix duplicate VendorExtensions * Improve description * Fix removal of RequestMapping version attribute if x-spring-api-version: '' is set at the operation level
This commit is contained in:
parent
1d7d399ec2
commit
d7b79a26c6
@ -95,6 +95,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|
|||||||
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|
||||||
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|
||||||
|sourceFolder|source folder for generated code| |src/main/java|
|
|sourceFolder|source folder for generated code| |src/main/java|
|
||||||
|
|springApiVersion|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).| |null|
|
||||||
|testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi|
|
|testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi|
|
||||||
|title|server title name or client service name| |OpenAPI Spring|
|
|title|server title name or client service name| |OpenAPI Spring|
|
||||||
|unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false|
|
|unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false|
|
||||||
@ -132,6 +133,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|
|||||||
|x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false
|
|x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false
|
||||||
|x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null
|
|x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null
|
||||||
|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null
|
|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null
|
||||||
|
|x-spring-api-version|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).|OPERATION|null
|
||||||
|
|
||||||
|
|
||||||
## IMPORT MAPPING
|
## IMPORT MAPPING
|
||||||
|
@ -88,6 +88,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|
|||||||
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|
||||||
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|
||||||
|sourceFolder|source folder for generated code| |src/main/java|
|
|sourceFolder|source folder for generated code| |src/main/java|
|
||||||
|
|springApiVersion|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).| |null|
|
||||||
|testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi|
|
|testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi|
|
||||||
|title|server title name or client service name| |OpenAPI Spring|
|
|title|server title name or client service name| |OpenAPI Spring|
|
||||||
|unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false|
|
|unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false|
|
||||||
@ -125,6 +126,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|
|||||||
|x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false
|
|x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false
|
||||||
|x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null
|
|x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null
|
||||||
|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null
|
|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null
|
||||||
|
|x-spring-api-version|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).|OPERATION|null
|
||||||
|
|
||||||
|
|
||||||
## IMPORT MAPPING
|
## IMPORT MAPPING
|
||||||
|
@ -13,6 +13,7 @@ public enum VendorExtension {
|
|||||||
X_KOTLIN_IMPLEMENTS("x-kotlin-implements", ExtensionLevel.MODEL, "Ability to specify interfaces that model must implement", "empty array"),
|
X_KOTLIN_IMPLEMENTS("x-kotlin-implements", ExtensionLevel.MODEL, "Ability to specify interfaces that model must implement", "empty array"),
|
||||||
X_KOTLIN_IMPLEMENTS_FIELDS("x-kotlin-implements-fields", ExtensionLevel.MODEL, "Specify attributes that are implemented by the interface(s) added via `x-kotlin-implements`", "empty array"),
|
X_KOTLIN_IMPLEMENTS_FIELDS("x-kotlin-implements-fields", ExtensionLevel.MODEL, "Specify attributes that are implemented by the interface(s) added via `x-kotlin-implements`", "empty array"),
|
||||||
X_SPRING_PAGINATED("x-spring-paginated", ExtensionLevel.OPERATION, "Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.", "false"),
|
X_SPRING_PAGINATED("x-spring-paginated", ExtensionLevel.OPERATION, "Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.", "false"),
|
||||||
|
X_SPRING_API_VERSION("x-spring-api-version", ExtensionLevel.OPERATION, "Value for 'version' attribute in @RequestMapping (for Spring 7 and above).", null),
|
||||||
X_SPRING_PROVIDE_ARGS("x-spring-provide-args", ExtensionLevel.OPERATION, "Allows adding additional hidden parameters in the API specification to allow access to content such as header values or properties", "empty array"),
|
X_SPRING_PROVIDE_ARGS("x-spring-provide-args", ExtensionLevel.OPERATION, "Allows adding additional hidden parameters in the API specification to allow access to content such as header values or properties", "empty array"),
|
||||||
X_DISCRIMINATOR_VALUE("x-discriminator-value", ExtensionLevel.MODEL, "Used with model inheritance to specify value for discriminator that identifies current model", ""),
|
X_DISCRIMINATOR_VALUE("x-discriminator-value", ExtensionLevel.MODEL, "Used with model inheritance to specify value for discriminator that identifies current model", ""),
|
||||||
X_SETTER_EXTRA_ANNOTATION("x-setter-extra-annotation", ExtensionLevel.FIELD, "Custom annotation that can be specified over java setter for specific field", "When field is array & uniqueItems, then this extension is used to add `@JsonDeserialize(as = LinkedHashSet.class)` over setter, otherwise no value"),
|
X_SETTER_EXTRA_ANNOTATION("x-setter-extra-annotation", ExtensionLevel.FIELD, "Custom annotation that can be specified over java setter for specific field", "When field is array & uniqueItems, then this extension is used to add `@JsonDeserialize(as = LinkedHashSet.class)` over setter, otherwise no value"),
|
||||||
|
@ -100,6 +100,7 @@ public class SpringCodegen extends AbstractJavaCodegen
|
|||||||
public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable";
|
public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable";
|
||||||
public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation";
|
public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation";
|
||||||
public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces";
|
public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces";
|
||||||
|
public static final String SPRING_API_VERSION = "springApiVersion";
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public enum RequestMappingMode {
|
public enum RequestMappingMode {
|
||||||
@ -286,6 +287,7 @@ public class SpringCodegen extends AbstractJavaCodegen
|
|||||||
optionalAcceptNullable));
|
optionalAcceptNullable));
|
||||||
|
|
||||||
cliOptions.add(CliOption.newBoolean(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "whether to use deduction for generated oneOf interfaces", useDeductionForOneOfInterfaces));
|
cliOptions.add(CliOption.newBoolean(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "whether to use deduction for generated oneOf interfaces", useDeductionForOneOfInterfaces));
|
||||||
|
cliOptions.add(CliOption.newString(SPRING_API_VERSION, "Value for 'version' attribute in @RequestMapping (for Spring 7 and above)."));
|
||||||
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
|
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
|
||||||
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
|
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
|
||||||
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
|
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
|
||||||
@ -855,6 +857,8 @@ public class SpringCodegen extends AbstractJavaCodegen
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void prepareVersioningParameters(List<CodegenOperation> operations) {
|
private void prepareVersioningParameters(List<CodegenOperation> operations) {
|
||||||
|
Object apiVersion = additionalProperties.get(SPRING_API_VERSION);
|
||||||
|
boolean hasApiVersion = apiVersion != null;
|
||||||
for (CodegenOperation operation : operations) {
|
for (CodegenOperation operation : operations) {
|
||||||
if (operation.getHasHeaderParams()) {
|
if (operation.getHasHeaderParams()) {
|
||||||
List<CodegenParameter> versionParams = operation.headerParams.stream()
|
List<CodegenParameter> versionParams = operation.headerParams.stream()
|
||||||
@ -877,6 +881,9 @@ public class SpringCodegen extends AbstractJavaCodegen
|
|||||||
operation.hasVersionQueryParams = !versionParams.isEmpty();
|
operation.hasVersionQueryParams = !versionParams.isEmpty();
|
||||||
operation.vendorExtensions.put("versionQueryParamsList", versionParams);
|
operation.vendorExtensions.put("versionQueryParamsList", versionParams);
|
||||||
}
|
}
|
||||||
|
if (hasApiVersion) {
|
||||||
|
operation.vendorExtensions.putIfAbsent(VendorExtension.X_SPRING_API_VERSION.getName(), apiVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1205,6 +1212,7 @@ public class SpringCodegen extends AbstractJavaCodegen
|
|||||||
extensions.add(VendorExtension.X_SPRING_PAGINATED);
|
extensions.add(VendorExtension.X_SPRING_PAGINATED);
|
||||||
extensions.add(VendorExtension.X_VERSION_PARAM);
|
extensions.add(VendorExtension.X_VERSION_PARAM);
|
||||||
extensions.add(VendorExtension.X_PATTERN_MESSAGE);
|
extensions.add(VendorExtension.X_PATTERN_MESSAGE);
|
||||||
|
extensions.add(VendorExtension.X_SPRING_API_VERSION);
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,8 @@ public interface {{classname}} {
|
|||||||
produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}},
|
produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}},
|
||||||
consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}},
|
consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}},
|
||||||
headers = { {{#vendorExtensions.versionHeaderParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionHeaderParamsList}} } {{/hasVersionHeaders}}{{#hasVersionQueryParams}},
|
headers = { {{#vendorExtensions.versionHeaderParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionHeaderParamsList}} } {{/hasVersionHeaders}}{{#hasVersionQueryParams}},
|
||||||
params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}
|
params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}{{#vendorExtensions.x-spring-api-version}}{{^empty}},
|
||||||
|
version = "{{{vendorExtensions.x-spring-api-version}}}"{{/empty}}{{/vendorExtensions.x-spring-api-version}}
|
||||||
)
|
)
|
||||||
{{^useResponseEntity}}
|
{{^useResponseEntity}}
|
||||||
@ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}})
|
@ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}})
|
||||||
|
@ -50,25 +50,42 @@ public abstract class AbstractAnnotationsAssert<ACTUAL extends AbstractAnnotatio
|
|||||||
return myself();
|
return myself();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ACTUAL containsWithNameAndDoesContainAttributes(final String name, final List<String> attributes) {
|
||||||
|
super
|
||||||
|
.withFailMessage("Should have annotation with name: " + name + " and no attributes: " + attributes + ", but was: " + actual)
|
||||||
|
.anyMatch(annotation -> annotation.getNameAsString().equals(name) && hasNotAttributes(annotation, attributes));
|
||||||
|
return myself();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasNotAttributes(final AnnotationExpr annotation, final List<String> attributes) {
|
||||||
|
final Map<String, String> actualAttributes = getAttributes(annotation);
|
||||||
|
|
||||||
|
return actualAttributes.keySet().stream()
|
||||||
|
.noneMatch(attribute -> attributes.contains(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean hasAttributes(final AnnotationExpr annotation, final Map<String, String> expectedAttributesToContains) {
|
private static boolean hasAttributes(final AnnotationExpr annotation, final Map<String, String> expectedAttributesToContains) {
|
||||||
final Map<String, String> actualAttributes;
|
final Map<String, String> actualAttributes = getAttributes(annotation);
|
||||||
if (annotation instanceof SingleMemberAnnotationExpr) {
|
|
||||||
actualAttributes = ImmutableMap.of(
|
|
||||||
"value", ((SingleMemberAnnotationExpr) annotation).getMemberValue().toString()
|
|
||||||
);
|
|
||||||
} else if (annotation instanceof NormalAnnotationExpr) {
|
|
||||||
actualAttributes = ((NormalAnnotationExpr) annotation).getPairs().stream()
|
|
||||||
.collect(Collectors.toMap(NodeWithSimpleName::getNameAsString, pair -> pair.getValue().toString()));
|
|
||||||
} else if (annotation instanceof MarkerAnnotationExpr) {
|
|
||||||
actualAttributes = new HashMap<>();
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unexpected annotation expression type for: " + annotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expectedAttributesToContains.entrySet().stream()
|
return expectedAttributesToContains.entrySet().stream()
|
||||||
.allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue()));
|
.allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> getAttributes(final AnnotationExpr annotation) {
|
||||||
|
if (annotation instanceof SingleMemberAnnotationExpr) {
|
||||||
|
return ImmutableMap.of(
|
||||||
|
"value", ((SingleMemberAnnotationExpr) annotation).getMemberValue().toString()
|
||||||
|
);
|
||||||
|
} else if (annotation instanceof NormalAnnotationExpr) {
|
||||||
|
return ((NormalAnnotationExpr) annotation).getPairs().stream()
|
||||||
|
.collect(Collectors.toMap(NodeWithSimpleName::getNameAsString, pair -> pair.getValue().toString()));
|
||||||
|
} else if (annotation instanceof MarkerAnnotationExpr) {
|
||||||
|
return new HashMap<>();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unexpected annotation expression type for: " + annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private ACTUAL myself() {
|
private ACTUAL myself() {
|
||||||
return (ACTUAL) this;
|
return (ACTUAL) this;
|
||||||
|
@ -5738,4 +5738,25 @@ public class SpringCodegenTest {
|
|||||||
.isInterface()
|
.isInterface()
|
||||||
.assertTypeAnnotations().containsWithName("SuppressWarnings");
|
.assertTypeAnnotations().containsWithName("SuppressWarnings");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApiVersion() throws IOException {
|
||||||
|
final Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/apiVersion.yaml", SPRING_BOOT,
|
||||||
|
Map.of(SpringCodegen.SPRING_API_VERSION, "v1",
|
||||||
|
USE_TAGS, true));
|
||||||
|
JavaFileAssert.assertThat(files.get("TestApi.java"))
|
||||||
|
.assertMethod("getVersions")
|
||||||
|
.assertMethodAnnotations()
|
||||||
|
.containsWithNameAndAttributes("RequestMapping", Map.of("version", "\"v1\""))
|
||||||
|
.toMethod().toFileAssert()
|
||||||
|
|
||||||
|
.assertMethod("getOverrides")
|
||||||
|
.assertMethodAnnotations()
|
||||||
|
.containsWithNameAndAttributes("RequestMapping", Map.of("version", "\"2+\""))
|
||||||
|
.toMethod().toFileAssert()
|
||||||
|
|
||||||
|
.assertMethod("getNones")
|
||||||
|
.assertMethodAnnotations()
|
||||||
|
.containsWithNameAndDoesContainAttributes("RequestMapping", List.of("version"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: x-spring-api-version test
|
||||||
|
version: 1.0.0
|
||||||
|
paths:
|
||||||
|
/versions:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Test
|
||||||
|
operationId: getVersions
|
||||||
|
/overrides:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Test
|
||||||
|
operationId: getOverrides
|
||||||
|
x-spring-api-version: '2+'
|
||||||
|
/nones:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Test
|
||||||
|
operationId: getNones
|
||||||
|
x-spring-api-version: ''
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user