JAX-RS Spec: Stop generating @NotNull Validation for required & optional properties (#12575)

* Stop generating @NotNull Validation for required & optional properties

* correct linebreak and add tests

* add samples in a separate folder
This commit is contained in:
Yannick Wiesner 2023-03-13 17:39:31 +01:00 committed by GitHub
parent e53b6fa7fa
commit ecd28b2090
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 568 additions and 4 deletions

View File

@ -0,0 +1,10 @@
generatorName: jaxrs-spec
outputDir: samples/server/petstore/jaxrs-spec-required-and-readonly-property
inputSpec: modules/openapi-generator/src/test/resources/3_0/required-and-readonly-property.yaml
templateDir: modules/openapi-generator/src/main/resources/JavaJaxRS/spec
additionalProperties:
artifactId: jaxrs-spec-petstore-server
serializableModel: "true"
hideGenerationTimestamp: "true"
implicitHeadersRegex: (api_key|enum_header_string)
generateBuilders: "true"

View File

@ -1,4 +1,2 @@
{{#required}} {{#required}}{{^isReadOnly}} @NotNull
@NotNull {{/isReadOnly}}{{/required}}{{>beanValidationCore}}
{{/required}}
{{>beanValidationCore}}

View File

@ -6,6 +6,7 @@ import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.ParseOptions;
import org.assertj.core.condition.AllOf;
import org.openapitools.codegen.*; import org.openapitools.codegen.*;
import org.openapitools.codegen.config.CodegenConfigurator; import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.java.assertions.JavaFileAssert; import org.openapitools.codegen.java.assertions.JavaFileAssert;
@ -754,4 +755,44 @@ public class JavaJAXRSSpecServerCodegenTest extends JavaJaxrsBaseTest {
"@GZIP\n" "@GZIP\n"
); );
} }
@Test
public void testHandleRequiredAndReadOnlyPropertiesCorrectly() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/required-and-readonly-property.yaml", null, new ParseOptions()).getOpenAPI();
codegen.setOutputDir(output.getAbsolutePath());
ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);
DefaultGenerator generator = new DefaultGenerator();
Map<String, File> files = generator.opts(input).generate().stream()
.collect(Collectors.toMap(File::getName, Function.identity()));
JavaFileAssert.assertThat(files.get("ReadonlyAndRequiredProperties.java"))
.hasProperty("requiredYesReadonlyYes")
.toType()
.assertMethod("getRequiredYesReadonlyYes")
.assertMethodAnnotations()
.hasSize(2)
.containsWithNameAndAttributes("ApiModelProperty", ImmutableMap.of("required", "true"))
// Mysteriously, but we need to surround the value with quotes if the Annotation only contains a single value
.containsWithNameAndAttributes("JsonProperty", ImmutableMap.of("value", "\"requiredYesReadonlyYes\""))
.toMethod()
.toFileAssert()
.hasProperty("requiredYesReadonlyNo")
.toType()
.assertMethod("getRequiredYesReadonlyNo")
.assertMethodAnnotations()
.hasSize(3)
.containsWithNameAndAttributes("ApiModelProperty", ImmutableMap.of("required", "true"))
// Mysteriously, but we need to surround the value with quotes if the Annotation only contains a single value
.containsWithNameAndAttributes("JsonProperty", ImmutableMap.of("value", "\"requiredYesReadonlyNo\""))
.containsWithName("NotNull");
}
} }

View File

@ -0,0 +1,38 @@
openapi: 3.0.3
info:
title: Title
description: Title
version: 1.0.0
servers:
- url: 'https'
paths:
'/user':
get:
responses:
200:
description: "success"
content:
application/json:
schema:
$ref: '#/components/schemas/ReadonlyAndRequiredProperties'
components:
schemas:
ReadonlyAndRequiredProperties:
type: object
required:
- requiredYesReadonlyYes
- requiredYesReadonlyNo
properties:
requiredYesReadonlyYes:
type: string
readOnly: true
requiredYesReadonlyNo:
type: string
requiredNoReadonlyYes:
type: string
readOnly: true
requiredNoReadonlyNo:
type: string

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,7 @@
.openapi-generator-ignore
README.md
pom.xml
src/gen/java/org/openapitools/api/RestApplication.java
src/gen/java/org/openapitools/api/UserApi.java
src/gen/java/org/openapitools/model/ReadonlyAndRequiredProperties.java
src/main/openapi/openapi.yaml

View File

@ -0,0 +1,27 @@
# JAX-RS server with OpenAPI
## Overview
This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using an
[OpenAPI-Spec](https://openapis.org), you can easily generate a server stub.
This is an example of building a OpenAPI-enabled JAX-RS server.
This example uses the [JAX-RS](https://jax-rs-spec.java.net/) framework.
The JAX-RS implementation needs to be provided by the application server you are deploying on.
To run the server from the command line, you can use maven to provision an start a TomEE Server.
Please execute the following:
```
mvn -Dtomee-embedded-plugin.http=8080 package org.apache.tomee.maven:tomee-embedded-maven-plugin:7.0.5:run
```
You can then call your server endpoints under:
```
http://localhost:8080/
```
Note that if you have configured the `host` to be something other than localhost, the calls through
swagger-ui will be directed to that host and not localhost!

View File

@ -0,0 +1,139 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openapitools</groupId>
<artifactId>jaxrs-spec-petstore-server</artifactId>
<packaging>war</packaging>
<name>jaxrs-spec-petstore-server</name>
<version>1.0.0</version>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/gen/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>${jakarta.ws.rs-version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax.annotation-api-version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<scope>provided</scope>
<version>1.5.3</version>
</dependency>
<!-- @Nullable annotation -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.8</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
<exclusion>
<artifactId>snakeyaml</artifactId>
<groupId>org.yaml</groupId>
</exclusion>
<exclusion>
<artifactId>bsh</artifactId>
<groupId>org.beanshell</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- Bean Validation API support -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${beanvalidation-version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jackson-version>2.9.9</jackson-version>
<junit-version>4.13.2</junit-version>
<joda-version>2.10.13</joda-version>
<javax.annotation-api-version>1.3.2</javax.annotation-api-version>
<beanvalidation-version>2.0.2</beanvalidation-version>
<jakarta.ws.rs-version>2.1.6</jakarta.ws.rs-version>
</properties>
</project>

View File

@ -0,0 +1,9 @@
package org.openapitools.api;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("")
public class RestApplication extends Application {
}

View File

@ -0,0 +1,30 @@
package org.openapitools.api;
import org.openapitools.model.ReadonlyAndRequiredProperties;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import io.swagger.annotations.*;
import java.io.InputStream;
import java.util.Map;
import java.util.List;
import javax.validation.constraints.*;
import javax.validation.Valid;
@Path("/user")
@Api(description = "the user API")
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen")
public class UserApi {
@GET
@Produces({ "application/json" })
@ApiOperation(value = "", notes = "", response = ReadonlyAndRequiredProperties.class, tags={ })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "success", response = ReadonlyAndRequiredProperties.class)
})
public Response userGet() {
return Response.ok().entity("magic!").build();
}
}

View File

@ -0,0 +1,200 @@
package org.openapitools.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.*;
import javax.validation.Valid;
import io.swagger.annotations.*;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.annotation.JsonTypeName;
@JsonTypeName("ReadonlyAndRequiredProperties")
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen")
public class ReadonlyAndRequiredProperties implements Serializable {
private @Valid String requiredYesReadonlyYes;
private @Valid String requiredYesReadonlyNo;
private @Valid String requiredNoReadonlyYes;
private @Valid String requiredNoReadonlyNo;
protected ReadonlyAndRequiredProperties(ReadonlyAndRequiredPropertiesBuilder<?, ?> b) {
this.requiredYesReadonlyYes = b.requiredYesReadonlyYes;this.requiredYesReadonlyNo = b.requiredYesReadonlyNo;this.requiredNoReadonlyYes = b.requiredNoReadonlyYes;this.requiredNoReadonlyNo = b.requiredNoReadonlyNo;
}
public ReadonlyAndRequiredProperties() { }
/**
**/
public ReadonlyAndRequiredProperties requiredYesReadonlyYes(String requiredYesReadonlyYes) {
this.requiredYesReadonlyYes = requiredYesReadonlyYes;
return this;
}
@ApiModelProperty(required = true, value = "")
@JsonProperty("requiredYesReadonlyYes")
public String getRequiredYesReadonlyYes() {
return requiredYesReadonlyYes;
}
@JsonProperty("requiredYesReadonlyYes")
public void setRequiredYesReadonlyYes(String requiredYesReadonlyYes) {
this.requiredYesReadonlyYes = requiredYesReadonlyYes;
}
/**
**/
public ReadonlyAndRequiredProperties requiredYesReadonlyNo(String requiredYesReadonlyNo) {
this.requiredYesReadonlyNo = requiredYesReadonlyNo;
return this;
}
@ApiModelProperty(required = true, value = "")
@JsonProperty("requiredYesReadonlyNo")
@NotNull
public String getRequiredYesReadonlyNo() {
return requiredYesReadonlyNo;
}
@JsonProperty("requiredYesReadonlyNo")
public void setRequiredYesReadonlyNo(String requiredYesReadonlyNo) {
this.requiredYesReadonlyNo = requiredYesReadonlyNo;
}
/**
**/
public ReadonlyAndRequiredProperties requiredNoReadonlyYes(String requiredNoReadonlyYes) {
this.requiredNoReadonlyYes = requiredNoReadonlyYes;
return this;
}
@ApiModelProperty(value = "")
@JsonProperty("requiredNoReadonlyYes")
public String getRequiredNoReadonlyYes() {
return requiredNoReadonlyYes;
}
@JsonProperty("requiredNoReadonlyYes")
public void setRequiredNoReadonlyYes(String requiredNoReadonlyYes) {
this.requiredNoReadonlyYes = requiredNoReadonlyYes;
}
/**
**/
public ReadonlyAndRequiredProperties requiredNoReadonlyNo(String requiredNoReadonlyNo) {
this.requiredNoReadonlyNo = requiredNoReadonlyNo;
return this;
}
@ApiModelProperty(value = "")
@JsonProperty("requiredNoReadonlyNo")
public String getRequiredNoReadonlyNo() {
return requiredNoReadonlyNo;
}
@JsonProperty("requiredNoReadonlyNo")
public void setRequiredNoReadonlyNo(String requiredNoReadonlyNo) {
this.requiredNoReadonlyNo = requiredNoReadonlyNo;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ReadonlyAndRequiredProperties readonlyAndRequiredProperties = (ReadonlyAndRequiredProperties) o;
return Objects.equals(this.requiredYesReadonlyYes, readonlyAndRequiredProperties.requiredYesReadonlyYes) &&
Objects.equals(this.requiredYesReadonlyNo, readonlyAndRequiredProperties.requiredYesReadonlyNo) &&
Objects.equals(this.requiredNoReadonlyYes, readonlyAndRequiredProperties.requiredNoReadonlyYes) &&
Objects.equals(this.requiredNoReadonlyNo, readonlyAndRequiredProperties.requiredNoReadonlyNo);
}
@Override
public int hashCode() {
return Objects.hash(requiredYesReadonlyYes, requiredYesReadonlyNo, requiredNoReadonlyYes, requiredNoReadonlyNo);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ReadonlyAndRequiredProperties {\n");
sb.append(" requiredYesReadonlyYes: ").append(toIndentedString(requiredYesReadonlyYes)).append("\n");
sb.append(" requiredYesReadonlyNo: ").append(toIndentedString(requiredYesReadonlyNo)).append("\n");
sb.append(" requiredNoReadonlyYes: ").append(toIndentedString(requiredNoReadonlyYes)).append("\n");
sb.append(" requiredNoReadonlyNo: ").append(toIndentedString(requiredNoReadonlyNo)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
public static ReadonlyAndRequiredPropertiesBuilder<?, ?> builder() {
return new ReadonlyAndRequiredPropertiesBuilderImpl();
}
private static final class ReadonlyAndRequiredPropertiesBuilderImpl extends ReadonlyAndRequiredPropertiesBuilder<ReadonlyAndRequiredProperties, ReadonlyAndRequiredPropertiesBuilderImpl> {
@Override
protected ReadonlyAndRequiredPropertiesBuilderImpl self() {
return this;
}
@Override
public ReadonlyAndRequiredProperties build() {
return new ReadonlyAndRequiredProperties(this);
}
}
public static abstract class ReadonlyAndRequiredPropertiesBuilder<C extends ReadonlyAndRequiredProperties, B extends ReadonlyAndRequiredPropertiesBuilder<C, B>> {
private String requiredYesReadonlyYes;
private String requiredYesReadonlyNo;
private String requiredNoReadonlyYes;
private String requiredNoReadonlyNo;
protected abstract B self();
public abstract C build();
public B requiredYesReadonlyYes(String requiredYesReadonlyYes) {
this.requiredYesReadonlyYes = requiredYesReadonlyYes;
return self();
}
public B requiredYesReadonlyNo(String requiredYesReadonlyNo) {
this.requiredYesReadonlyNo = requiredYesReadonlyNo;
return self();
}
public B requiredNoReadonlyYes(String requiredNoReadonlyYes) {
this.requiredNoReadonlyYes = requiredNoReadonlyYes;
return self();
}
public B requiredNoReadonlyNo(String requiredNoReadonlyNo) {
this.requiredNoReadonlyNo = requiredNoReadonlyNo;
return self();
}
}
}

View File

@ -0,0 +1,41 @@
openapi: 3.0.3
info:
description: Title
title: Title
version: 1.0.0
servers:
- url: https
paths:
/user:
get:
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/ReadonlyAndRequiredProperties'
description: success
x-accepts: application/json
components:
schemas:
ReadonlyAndRequiredProperties:
example:
requiredYesReadonlyYes: requiredYesReadonlyYes
requiredNoReadonlyYes: requiredNoReadonlyYes
requiredYesReadonlyNo: requiredYesReadonlyNo
requiredNoReadonlyNo: requiredNoReadonlyNo
properties:
requiredYesReadonlyYes:
readOnly: true
type: string
requiredYesReadonlyNo:
type: string
requiredNoReadonlyYes:
readOnly: true
type: string
requiredNoReadonlyNo:
type: string
required:
- requiredYesReadonlyNo
- requiredYesReadonlyYes
type: object