forked from loafle/openapi-generator-original
[Java] fix JsonCreator with JsonNullable (#12813)
This commit is contained in:
@@ -2053,6 +2053,22 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for property by {@link CodegenProperty#name}
|
||||
* @param name - name to search for
|
||||
* @param properties - list of properties
|
||||
* @return either found property or {@link Optional#empty()} if nothing has been found
|
||||
*/
|
||||
protected Optional<CodegenProperty> findByName(String name, List<CodegenProperty> properties) {
|
||||
if (properties == null || properties.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return properties.stream()
|
||||
.filter(p -> p.name.equals(name))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method removes all implicit header parameters from the list of parameters
|
||||
*
|
||||
|
||||
@@ -159,6 +159,8 @@ public class JavaCXFClientCodegen extends AbstractJavaCodegen
|
||||
if (openApiNullable) {
|
||||
if (Boolean.FALSE.equals(property.required) && Boolean.TRUE.equals(property.isNullable)) {
|
||||
property.getVendorExtensions().put("x-is-jackson-optional-nullable", true);
|
||||
findByName(property.name, model.readOnlyVars)
|
||||
.ifPresent(p -> p.getVendorExtensions().put("x-is-jackson-optional-nullable", true));
|
||||
model.imports.add("JsonNullable");
|
||||
model.imports.add("JsonIgnore");
|
||||
}
|
||||
|
||||
@@ -926,6 +926,8 @@ public class JavaClientCodegen extends AbstractJavaCodegen
|
||||
// only add JsonNullable and related imports to optional and nullable values
|
||||
addImports |= isOptionalNullable;
|
||||
var.getVendorExtensions().put("x-is-jackson-optional-nullable", isOptionalNullable);
|
||||
findByName(var.name, cm.readOnlyVars)
|
||||
.ifPresent(p -> p.getVendorExtensions().put("x-is-jackson-optional-nullable", isOptionalNullable));
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(var.getVendorExtensions().get("x-enum-as-string"))) {
|
||||
|
||||
@@ -100,7 +100,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
|
||||
) {
|
||||
this();
|
||||
{{#readOnlyVars}}
|
||||
this.{{name}} = {{name}};
|
||||
this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}};
|
||||
{{/readOnlyVars}}
|
||||
}{{/jackson}}{{/withXml}}{{/vendorExtensions.x-has-readonly-properties}}
|
||||
{{#vars}}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
|
||||
) {
|
||||
this();
|
||||
{{#readOnlyVars}}
|
||||
this.{{name}} = {{name}};
|
||||
this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}};
|
||||
{{/readOnlyVars}}
|
||||
}{{/withXml}}{{/vendorExtensions.x-has-readonly-properties}}
|
||||
{{#vars}}
|
||||
|
||||
@@ -93,12 +93,12 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
|
||||
{{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}}
|
||||
public {{classname}}(
|
||||
{{#readOnlyVars}}
|
||||
{{#jsonb}}@JsonbProperty("{{baseName}}"){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}
|
||||
{{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nillable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}
|
||||
{{/readOnlyVars}}
|
||||
) {
|
||||
this();
|
||||
{{#readOnlyVars}}
|
||||
this.{{name}} = {{name}};
|
||||
this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}};
|
||||
{{/readOnlyVars}}
|
||||
}{{/withXml}}{{/vendorExtensions.x-has-readonly-properties}}
|
||||
{{#vars}}
|
||||
|
||||
@@ -1534,10 +1534,42 @@ public class JavaClientCodegenTest {
|
||||
.collect(Collectors.toMap(File::getName, Function.identity()));
|
||||
|
||||
JavaFileAssert.assertThat(files.get("Foo.java"))
|
||||
.assertConstructor("String", "Integer")
|
||||
.hasParameter("b")
|
||||
.assertParameterAnnotations()
|
||||
.containsWithNameAndAttributes("JsonbProperty", ImmutableMap.of("value", "\"b\"", "nillable", "true"))
|
||||
.toParameter().toConstructor()
|
||||
.hasParameter("c")
|
||||
.assertParameterAnnotations()
|
||||
.containsWithNameAndAttributes("JsonbProperty", ImmutableMap.of("value", "\"c\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebClientJsonCreatorWithNullable_issue12790() throws Exception {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(AbstractJavaCodegen.OPENAPI_NULLABLE, "true");
|
||||
|
||||
File output = Files.createTempDirectory("test").toFile();
|
||||
output.deleteOnExit();
|
||||
|
||||
final CodegenConfigurator configurator = new CodegenConfigurator()
|
||||
.setAdditionalProperties(properties)
|
||||
.setGeneratorName("java")
|
||||
.setLibrary(JavaClientCodegen.WEBCLIENT)
|
||||
.setInputSpec("src/test/resources/bugs/issue_12790.yaml")
|
||||
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
|
||||
|
||||
final ClientOptInput clientOptInput = configurator.toClientOptInput();
|
||||
DefaultGenerator generator = new DefaultGenerator();
|
||||
Map<String, File> files = generator.opts(clientOptInput).generate().stream()
|
||||
.collect(Collectors.toMap(File::getName, Function.identity()));
|
||||
|
||||
JavaFileAssert.assertThat(files.get("TestObject.java"))
|
||||
.printFileContent()
|
||||
.fileContains(
|
||||
"@JsonbProperty(value = \"b\", nillable = true) String b",
|
||||
"@JsonbProperty(value = \"c\") Integer c"
|
||||
.assertConstructor("String", "String")
|
||||
.bodyContainsLines(
|
||||
"this.nullableProperty = nullableProperty == null ? JsonNullable.<String>undefined() : JsonNullable.of(nullableProperty);",
|
||||
"this.notNullableProperty = notNullableProperty;"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.openapitools.codegen.java.assertions;
|
||||
|
||||
import com.github.javaparser.ast.body.ConstructorDeclaration;
|
||||
import com.github.javaparser.ast.body.Parameter;
|
||||
import com.github.javaparser.ast.nodeTypes.NodeWithName;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.util.CanIgnoreReturnValue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
public class ConstructorAssert extends AbstractAssert<ConstructorAssert, ConstructorDeclaration> {
|
||||
|
||||
private final JavaFileAssert fileAssert;
|
||||
private final String signature;
|
||||
|
||||
ConstructorAssert(final JavaFileAssert fileAssert, final ConstructorDeclaration constructorDeclaration) {
|
||||
super(constructorDeclaration, ConstructorAssert.class);
|
||||
this.fileAssert = fileAssert;
|
||||
this.signature = constructorDeclaration.getDeclarationAsString();
|
||||
}
|
||||
|
||||
public JavaFileAssert toFileAssert() {
|
||||
return fileAssert;
|
||||
}
|
||||
|
||||
public MethodAnnotationAssert assertConstructorAnnotations() {
|
||||
return new MethodAnnotationAssert(this, actual.getAnnotations());
|
||||
}
|
||||
|
||||
public ParameterAssert hasParameter(final String paramName) {
|
||||
final Optional<Parameter> parameter = actual.getParameterByName(paramName);
|
||||
Assertions.assertThat(parameter)
|
||||
.withFailMessage("Constructor %s should have parameter %s, but it doesn't", signature, paramName)
|
||||
.isPresent();
|
||||
return new ParameterAssert(this, parameter.get());
|
||||
}
|
||||
|
||||
public ConstructorAssert doesNotHaveParameter(final String paramName) {
|
||||
Assertions.assertThat(actual.getParameterByName(paramName))
|
||||
.withFailMessage("Constructor %s shouldn't have parameter %s, but it does", signature, paramName)
|
||||
.isEmpty();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConstructorAssert bodyContainsLines(final String... lines) {
|
||||
final String actualBody = actual.getTokenRange()
|
||||
.orElseThrow(() -> new IllegalStateException("Can't get constructor body"))
|
||||
.toString();
|
||||
Assertions.assertThat(actualBody)
|
||||
.withFailMessage(
|
||||
"Constructor's %s body should contains lines\n====\n%s\n====\nbut actually was\n====\n%s\n====",
|
||||
signature, Arrays.stream(lines).collect(Collectors.joining(System.lineSeparator())), actualBody
|
||||
)
|
||||
.contains(lines);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConstructorAssert doesNotHaveComment() {
|
||||
Assertions.assertThat(actual.getJavadocComment())
|
||||
.withFailMessage("Constructor %s shouldn't contains comment, but it does", signature)
|
||||
.isEmpty();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConstructorAssert commentContainsLines(final String... lines) {
|
||||
Assertions.assertThat(actual.getJavadocComment())
|
||||
.withFailMessage("Constructor %s should contains comment, but it doesn't", signature)
|
||||
.isPresent();
|
||||
final String actualComment = actual.getJavadocComment().get().getContent();
|
||||
Assertions.assertThat(actualComment)
|
||||
.withFailMessage(
|
||||
"Constructor's %s comment should contains lines\n====\n%s\n====\nbut actually was\n====%s\n====",
|
||||
signature, Arrays.stream(lines).collect(Collectors.joining(System.lineSeparator())), actualComment
|
||||
)
|
||||
.contains(lines);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConstructorAssert noneOfParameterHasAnnotation(final String annotationName) {
|
||||
actual.getParameters()
|
||||
.forEach(
|
||||
param -> Assertions.assertThat(param.getAnnotations())
|
||||
.withFailMessage("Parameter %s contains annotation %s while it shouldn't", param.getNameAsString(), annotationName)
|
||||
.extracting(NodeWithName::getNameAsString)
|
||||
.doesNotContain(annotationName)
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.github.javaparser.ast.body.ConstructorDeclaration;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.util.CanIgnoreReturnValue;
|
||||
@@ -59,6 +60,15 @@ public class JavaFileAssert extends AbstractAssert<JavaFileAssert, CompilationUn
|
||||
return new MethodAssert(this, methods.get(0));
|
||||
}
|
||||
|
||||
public ConstructorAssert assertConstructor(final String... paramTypes) {
|
||||
Optional<ConstructorDeclaration> constructorDeclaration = actual.getType(0).getConstructorByParameterTypes(paramTypes);
|
||||
Assertions.assertThat(constructorDeclaration)
|
||||
.withFailMessage("No constructor with parameter(s) %s", Arrays.toString(paramTypes))
|
||||
.isPresent();
|
||||
|
||||
return new ConstructorAssert(this, constructorDeclaration.get());
|
||||
}
|
||||
|
||||
public PropertyAssert hasProperty(final String propertyName) {
|
||||
Optional<FieldDeclaration> fieldOptional = actual.getType(0).getMembers().stream()
|
||||
.filter(FieldDeclaration.class::isInstance)
|
||||
|
||||
@@ -10,13 +10,32 @@ import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
public class MethodAnnotationAssert extends AbstractAnnotationAssert<MethodAnnotationAssert> {
|
||||
|
||||
private final MethodAssert methodAssert;
|
||||
private final ConstructorAssert constructorAssert;
|
||||
|
||||
protected MethodAnnotationAssert(final MethodAssert methodAssert, final List<AnnotationExpr> annotationExpr) {
|
||||
super(annotationExpr);
|
||||
this.methodAssert = methodAssert;
|
||||
this.constructorAssert = null;
|
||||
}
|
||||
|
||||
protected MethodAnnotationAssert(final ConstructorAssert constructorAssert, final List<AnnotationExpr> annotationExpr) {
|
||||
super(annotationExpr);
|
||||
this.constructorAssert = constructorAssert;
|
||||
this.methodAssert = null;
|
||||
}
|
||||
|
||||
public MethodAssert toMethod() {
|
||||
if (methodAssert == null) {
|
||||
throw new IllegalArgumentException("No method assert for constructor's annotations");
|
||||
}
|
||||
return methodAssert;
|
||||
}
|
||||
|
||||
public ConstructorAssert toConstructor() {
|
||||
if (constructorAssert == null) {
|
||||
throw new IllegalArgumentException("No constructor assert for method's annotations");
|
||||
}
|
||||
return constructorAssert;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,16 +10,34 @@ import com.github.javaparser.ast.body.Parameter;
|
||||
public class ParameterAssert extends ObjectAssert<Parameter> {
|
||||
|
||||
private final MethodAssert methodAssert;
|
||||
private final ConstructorAssert constructorAssert;
|
||||
|
||||
protected ParameterAssert(final MethodAssert methodAssert, final Parameter parameter) {
|
||||
super(parameter);
|
||||
this.methodAssert = methodAssert;
|
||||
this.constructorAssert = null;
|
||||
}
|
||||
|
||||
protected ParameterAssert(final ConstructorAssert constructorAssert, final Parameter parameter) {
|
||||
super(parameter);
|
||||
this.constructorAssert = constructorAssert;
|
||||
this.methodAssert = null;
|
||||
}
|
||||
|
||||
public MethodAssert toMethod() {
|
||||
if (methodAssert == null) {
|
||||
throw new IllegalArgumentException("No method assert for constructor's parameter");
|
||||
}
|
||||
return methodAssert;
|
||||
}
|
||||
|
||||
public ConstructorAssert toConstructor() {
|
||||
if (constructorAssert == null) {
|
||||
throw new IllegalArgumentException("No constructor assert for method's parameter");
|
||||
}
|
||||
return constructorAssert;
|
||||
}
|
||||
|
||||
public ParameterAssert withType(final String expectedType) {
|
||||
Assertions.assertThat(actual.getTypeAsString())
|
||||
.withFailMessage("Expected parameter to have type %s, but was %s", expectedType, actual.getTypeAsString())
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Title
|
||||
version: "1"
|
||||
paths:
|
||||
/test:
|
||||
get:
|
||||
responses:
|
||||
200:
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TestObject'
|
||||
components:
|
||||
schemas:
|
||||
TestObject:
|
||||
type: object
|
||||
properties:
|
||||
nullableProperty:
|
||||
type: string
|
||||
nullable: true
|
||||
readOnly: true
|
||||
notNullableProperty:
|
||||
type: string
|
||||
readOnly: true
|
||||
notNullablePropertyNotRO:
|
||||
type: integer
|
||||
Reference in New Issue
Block a user