Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/generators/java-camel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|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|
|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|
Expand Down Expand Up @@ -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-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-spring-api-version|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).|OPERATION|null


## IMPORT MAPPING
Expand Down
2 changes: 2 additions & 0 deletions docs/generators/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|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|
|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|
Expand Down Expand Up @@ -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-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-spring-api-version|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).|OPERATION|null


## IMPORT MAPPING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_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_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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public class SpringCodegen extends AbstractJavaCodegen
public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable";
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 SPRING_API_VERSION = "springApiVersion";

@Getter
public enum RequestMappingMode {
Expand Down Expand Up @@ -286,6 +287,7 @@ public SpringCodegen() {
optionalAcceptNullable));

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_CLOUD_LIBRARY,
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
Expand Down Expand Up @@ -855,6 +857,8 @@ private void doDataTypeAssignment(String returnType, DataTypeAssigner dataTypeAs
}

private void prepareVersioningParameters(List<CodegenOperation> operations) {
Object apiVersion = additionalProperties.get(SPRING_API_VERSION);
boolean hasApiVersion = apiVersion != null;
for (CodegenOperation operation : operations) {
if (operation.getHasHeaderParams()) {
List<CodegenParameter> versionParams = operation.headerParams.stream()
Expand All @@ -877,6 +881,9 @@ private void prepareVersioningParameters(List<CodegenOperation> operations) {
operation.hasVersionQueryParams = !versionParams.isEmpty();
operation.vendorExtensions.put("versionQueryParamsList", versionParams);
}
if (hasApiVersion) {
operation.vendorExtensions.putIfAbsent(VendorExtension.X_SPRING_API_VERSION.getName(), apiVersion);
}
}
}

Expand Down Expand Up @@ -1205,6 +1212,7 @@ public List<VendorExtension> getSupportedVendorExtensions() {
extensions.add(VendorExtension.X_SPRING_PAGINATED);
extensions.add(VendorExtension.X_VERSION_PARAM);
extensions.add(VendorExtension.X_PATTERN_MESSAGE);
extensions.add(VendorExtension.X_SPRING_API_VERSION);
return extensions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ public interface {{classname}} {
produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}},
consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}},
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}}
@ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,40 @@ public ACTUAL containsWithNameAndAttributes(final String name, final Map<String,
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) {
final Map<String, String> actualAttributes;
final Map<String, String> actualAttributes = getAttributes(annotation);

return expectedAttributesToContains.entrySet().stream()
.allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue()));
}

private static Map<String, String> getAttributes(final AnnotationExpr annotation) {
if (annotation instanceof SingleMemberAnnotationExpr) {
actualAttributes = ImmutableMap.of(
return ImmutableMap.of(
"value", ((SingleMemberAnnotationExpr) annotation).getMemberValue().toString()
);
} else if (annotation instanceof NormalAnnotationExpr) {
actualAttributes = ((NormalAnnotationExpr) annotation).getPairs().stream()
return ((NormalAnnotationExpr) annotation).getPairs().stream()
.collect(Collectors.toMap(NodeWithSimpleName::getNameAsString, pair -> pair.getValue().toString()));
} else if (annotation instanceof MarkerAnnotationExpr) {
actualAttributes = new HashMap<>();
return new HashMap<>();
} else {
throw new IllegalArgumentException("Unexpected annotation expression type for: " + annotation);
}

return expectedAttributesToContains.entrySet().stream()
.allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue()));
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5738,4 +5738,25 @@ public void testOneOfInterfaceWithAnnotation() throws IOException {
.isInterface()
.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"));
}
}
Original file line number Diff line number Diff line change
@@ -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