Skip to content

Commit 20b5fdf

Browse files
committed
Add spring api versioning support
1 parent 89edad5 commit 20b5fdf

8 files changed

Lines changed: 88 additions & 8 deletions

File tree

docs/generators/java-camel.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
108108
|useOptional|Use Optional container for optional parameters| |false|
109109
|useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true|
110110
|useSealed|Whether to generate sealed model interfaces and classes| |false|
111+
|useSpringApiVersion|Generate version attribute in @RequestMapping for Spring 7.| |null|
111112
|useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false|
112113
|useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false|
113114
|useSpringController|Annotate the generated API as a Spring Controller| |false|
@@ -132,6 +133,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
132133
|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
133134
|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
134135
|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
136+
|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
137+
|x-spring-api-version|Generate version attribute in @RequestMapping for Spring 7.|OPERATION|null
135138

136139

137140
## IMPORT MAPPING

docs/generators/spring.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
101101
|useOptional|Use Optional container for optional parameters| |false|
102102
|useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true|
103103
|useSealed|Whether to generate sealed model interfaces and classes| |false|
104+
|useSpringApiVersion|Generate version attribute in @RequestMapping for Spring 7.| |null|
104105
|useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false|
105106
|useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false|
106107
|useSpringController|Annotate the generated API as a Spring Controller| |false|
@@ -125,6 +126,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
125126
|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
126127
|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
127128
|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
129+
|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
130+
|x-spring-api-version|Generate version attribute in @RequestMapping for Spring 7.|OPERATION|null
128131

129132

130133
## IMPORT MAPPING

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum VendorExtension {
1313
X_KOTLIN_IMPLEMENTS("x-kotlin-implements", ExtensionLevel.MODEL, "Ability to specify interfaces that model must implement", "empty array"),
1414
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"),
1515
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"),
16+
X_SPRING_API_VERSION("x-spring-api-version", ExtensionLevel.OPERATION, "Generate version attribute in @RequestMapping for Spring 7.", null),
1617
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"),
1718
X_DISCRIMINATOR_VALUE("x-discriminator-value", ExtensionLevel.MODEL, "Used with model inheritance to specify value for discriminator that identifies current model", ""),
1819
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"),

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public class SpringCodegen extends AbstractJavaCodegen
100100
public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable";
101101
public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation";
102102
public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces";
103+
public static final String USE_SPRING_API_VERSION = "useSpringApiVersion";
103104

104105
@Getter
105106
public enum RequestMappingMode {
@@ -286,6 +287,7 @@ public SpringCodegen() {
286287
optionalAcceptNullable));
287288

288289
cliOptions.add(CliOption.newBoolean(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "whether to use deduction for generated oneOf interfaces", useDeductionForOneOfInterfaces));
290+
cliOptions.add(CliOption.newString(USE_SPRING_API_VERSION, "Generate version attribute in @RequestMapping for Spring 7."));
289291
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
290292
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
291293
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
@@ -855,6 +857,8 @@ private void doDataTypeAssignment(String returnType, DataTypeAssigner dataTypeAs
855857
}
856858

857859
private void prepareVersioningParameters(List<CodegenOperation> operations) {
860+
String apiVersion = String.valueOf(additionalProperties.get(USE_SPRING_API_VERSION));
861+
boolean hasApiVersion = isNotEmpty(apiVersion);
858862
for (CodegenOperation operation : operations) {
859863
if (operation.getHasHeaderParams()) {
860864
List<CodegenParameter> versionParams = operation.headerParams.stream()
@@ -877,6 +881,11 @@ private void prepareVersioningParameters(List<CodegenOperation> operations) {
877881
operation.hasVersionQueryParams = !versionParams.isEmpty();
878882
operation.vendorExtensions.put("versionQueryParamsList", versionParams);
879883
}
884+
if (hasApiVersion) {
885+
if (!operation.vendorExtensions.remove(VendorExtension.X_SPRING_API_VERSION.getName(), "")) {
886+
operation.vendorExtensions.putIfAbsent(VendorExtension.X_SPRING_API_VERSION.getName(), apiVersion);
887+
}
888+
}
880889
}
881890
}
882891

@@ -1205,6 +1214,8 @@ public List<VendorExtension> getSupportedVendorExtensions() {
12051214
extensions.add(VendorExtension.X_SPRING_PAGINATED);
12061215
extensions.add(VendorExtension.X_VERSION_PARAM);
12071216
extensions.add(VendorExtension.X_PATTERN_MESSAGE);
1217+
extensions.add(VendorExtension.X_PATTERN_MESSAGE);
1218+
extensions.add(VendorExtension.X_SPRING_API_VERSION);
12081219
return extensions;
12091220
}
12101221
}

modules/openapi-generator/src/main/resources/JavaSpring/api.mustache

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ public interface {{classname}} {
252252
produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}},
253253
consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}},
254254
headers = { {{#vendorExtensions.versionHeaderParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionHeaderParamsList}} } {{/hasVersionHeaders}}{{#hasVersionQueryParams}},
255-
params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}
255+
params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}{{#vendorExtensions.x-spring-api-version}},
256+
version = "{{{vendorExtensions.x-spring-api-version}}}"{{/vendorExtensions.x-spring-api-version}}
256257
)
257258
{{^useResponseEntity}}
258259
@ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}})

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,40 @@ public ACTUAL containsWithNameAndAttributes(final String name, final Map<String,
5050
return myself();
5151
}
5252

53+
public ACTUAL containsWithNameAndDoesContainAttributes(final String name, final List<String> attributes) {
54+
super
55+
.withFailMessage("Should have annotation with name: " + name + " and no attributes: " + attributes + ", but was: " + actual)
56+
.anyMatch(annotation -> annotation.getNameAsString().equals(name) && hasNotAttributes(annotation, attributes));
57+
return myself();
58+
}
59+
60+
private static boolean hasNotAttributes(final AnnotationExpr annotation, final List<String> attributes) {
61+
final Map<String, String> actualAttributes = getAttributes(annotation);
62+
63+
return actualAttributes.entrySet().stream()
64+
.noneMatch(attribute -> attributes.contains(attribute));
65+
}
66+
5367
private static boolean hasAttributes(final AnnotationExpr annotation, final Map<String, String> expectedAttributesToContains) {
54-
final Map<String, String> actualAttributes;
68+
final Map<String, String> actualAttributes = getAttributes(annotation);
69+
70+
return expectedAttributesToContains.entrySet().stream()
71+
.allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue()));
72+
}
73+
74+
private static Map<String, String> getAttributes(final AnnotationExpr annotation) {
5575
if (annotation instanceof SingleMemberAnnotationExpr) {
56-
actualAttributes = ImmutableMap.of(
76+
return ImmutableMap.of(
5777
"value", ((SingleMemberAnnotationExpr) annotation).getMemberValue().toString()
5878
);
5979
} else if (annotation instanceof NormalAnnotationExpr) {
60-
actualAttributes = ((NormalAnnotationExpr) annotation).getPairs().stream()
80+
return ((NormalAnnotationExpr) annotation).getPairs().stream()
6181
.collect(Collectors.toMap(NodeWithSimpleName::getNameAsString, pair -> pair.getValue().toString()));
6282
} else if (annotation instanceof MarkerAnnotationExpr) {
63-
actualAttributes = new HashMap<>();
83+
return new HashMap<>();
6484
} else {
6585
throw new IllegalArgumentException("Unexpected annotation expression type for: " + annotation);
6686
}
67-
68-
return expectedAttributesToContains.entrySet().stream()
69-
.allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue()));
7087
}
7188

7289
@SuppressWarnings("unchecked")

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5738,4 +5738,25 @@ public void testOneOfInterfaceWithAnnotation() throws IOException {
57385738
.isInterface()
57395739
.assertTypeAnnotations().containsWithName("SuppressWarnings");
57405740
}
5741+
5742+
@Test
5743+
public void testApiVersion() throws IOException {
5744+
final Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/apiVersion.yaml", SPRING_BOOT,
5745+
Map.of(SpringCodegen.USE_SPRING_API_VERSION, "v1",
5746+
USE_TAGS, true));
5747+
JavaFileAssert.assertThat(files.get("TestApi.java"))
5748+
.assertMethod("getVersions")
5749+
.assertMethodAnnotations()
5750+
.containsWithNameAndAttributes("RequestMapping", Map.of("version", "\"v1\""))
5751+
.toMethod().toFileAssert()
5752+
5753+
.assertMethod("getOverrides")
5754+
.assertMethodAnnotations()
5755+
.containsWithNameAndAttributes("RequestMapping", Map.of("version", "\"2+\""))
5756+
.toMethod().toFileAssert()
5757+
5758+
.assertMethod("getNones")
5759+
.assertMethodAnnotations()
5760+
.containsWithNameAndDoesContainAttributes("RequestMapping", List.of("version"));
5761+
}
57415762
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
openapi: 3.0.0
2+
info:
3+
title: x-spring-api-version test
4+
version: 1.0.0
5+
paths:
6+
/versions:
7+
get:
8+
tags:
9+
- Test
10+
operationId: getVersions
11+
/overrides:
12+
get:
13+
tags:
14+
- Test
15+
operationId: getOverrides
16+
x-spring-api-version: '2+'
17+
/nones:
18+
get:
19+
tags:
20+
- Test
21+
operationId: getNones
22+
x-spring-api-version: ''
23+

0 commit comments

Comments
 (0)