Skip to content
Draft
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
5 changes: 4 additions & 1 deletion bin/configs/spring-boot-4-jspecify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ additionalProperties:
generateConstructorWithAllArgs: true
generateBuilders: true
openApiNullable: false
useJspecify: true
useJspecify: true
typeMappings:
OffsetDateTime: java.time.Instant
BigDecimal: java.math.BigDecimal
24 changes: 24 additions & 0 deletions bin/configs/spring-boot-4-jspecify_openapiNullable.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
generatorName: spring
library: spring-boot
outputDir: samples/openapi3/server/petstore/springboot-4-jspecify-openapiNullable
inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml
templateDir: modules/openapi-generator/src/main/resources/JavaSpring
validateSpec: false
additionalProperties:
groupId: org.openapitools.openapi3
documentationProvider: springdoc
interfaceOnly: true
artifactId: springboot
snapshotVersion: "true"
useSpringBoot4: true
useJackson3: true
useBeanValidation: true
withXml: true
hideGenerationTimestamp: "true"
generateConstructorWithAllArgs: true
generateBuilders: true
openApiNullable: true
useJspecify: true
typeMappings:
OffsetDateTime: java.time.Instant
BigDecimal: java.math.BigDecimal
25 changes: 25 additions & 0 deletions bin/configs/spring-boot-4-jspecify_useOptional.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
generatorName: spring
library: spring-boot
outputDir: samples/openapi3/server/petstore/springboot-4-jspecify-useOptional
inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml
templateDir: modules/openapi-generator/src/main/resources/JavaSpring
validateSpec: false
additionalProperties:
groupId: org.openapitools.openapi3
documentationProvider: springdoc
interfaceOnly: true
artifactId: springboot
snapshotVersion: "true"
useSpringBoot4: true
useJackson3: true
useBeanValidation: true
withXml: true
hideGenerationTimestamp: "true"
generateConstructorWithAllArgs: true
generateBuilders: true
useOptional: true
optionalAcceptNullable: true
useJspecify: true
typeMappings:
OffsetDateTime: java.time.Instant
BigDecimal: java.math.BigDecimal
Original file line number Diff line number Diff line change
Expand Up @@ -2866,7 +2866,7 @@ protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {
Mustache.Lambda jSpecifyDatatypeLambda = (fragment, writer) -> {
String dataType = fragment.execute();
if (jSpecifyNullableLambda.isSetAndClear()) {
int idx = dataType.lastIndexOf('.');
int idx = getLastIndex(dataType);
if (idx > 0) {
// generate declaration like java.time.@Nullable Timestamp
writer.write(dataType.substring(0, idx + 1));
Expand All @@ -2886,6 +2886,14 @@ protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {

}

private int getLastIndex(String dataType) {
int index = dataType.indexOf('<');
if (index >= 0) {
dataType = dataType.substring(0, index);
}
return dataType.lastIndexOf('.');
}

/**
* for Jspecify, remove @Nullable before the datatype and set keptNullable to true if done.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#lambda.trim}}{{>nullable_var_annotations}} {{#lambda.jSpecifyDatatype}}{{{datatypeWithEnum}}}{{/lambda.jSpecifyDatatype}}{{/lambda.trim}}
{{#lambda.trim}}{{>nullable_var_annotations}} {{>nullableDataTypeWithEnum}}{{/lambda.trim}}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
{{#deprecated}}
@Deprecated
{{/deprecated}}
public {{classname}}.Builder {{name}}({{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}) {
public {{classname}}.Builder {{name}}({{>nullableArgument_builder}} {{name}}) {
this.instance.{{name}}({{name}});
return this;
}
Expand All @@ -37,7 +37,7 @@
{{/vars}}
{{#parentVars}}
@Override
public {{classname}}.Builder {{name}}({{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}) {
public {{classname}}.Builder {{name}}({{>nullableArgument_builder}} {{name}}) {
this.instance.{{name}}({{name}});
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#lambda.jSpecifyNullable}}{{^required}}{{^defaultValue}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/defaultValue}}{{#defaultValue}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/defaultValue}}{{/required}}{{/lambda.jSpecifyNullable}}
{{#paramName}}{{>nullableAnnotation_default}}{{/paramName}}{{^paramName}}{{>nullableAnnotation_jspecify}}{{^useJspecify}}{{!backward compatibility}}{{>nullableAnnotation_default}}{{/useJspecify}}{{/paramName}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#lambda.jSpecifyNullable}}{{^required}}{{^defaultValue}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/defaultValue}}{{#defaultValue}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/defaultValue}}{{/required}}{{/lambda.jSpecifyNullable}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#useJspecify}}{{#lambda.jSpecifyNullable}}{{^required}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/required}}{{/lambda.jSpecifyNullable}}{{/useJspecify}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#useJspecify}}{{^required}}{{#lambda.jSpecifyNullable}}{{^useOptional}}@Nullable {{/useOptional}}{{#useOptional}}{{#optionalAcceptNullable}}@Nullable {{/optionalAcceptNullable}}{{/useOptional}}{{/lambda.jSpecifyNullable}}{{/required}}{{#lambda.jSpecifyDatatype}}{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}{{/lambda.jSpecifyDatatype}}{{/useJspecify}}{{^useJspecify}}{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}{{/useJspecify}}
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
/**
* Constructor with all args parameters
*/
public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{>nullableAnnotation}}{{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) {
public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{#useJspecify}}{{#lambda.jSpecifyNullable}}{{^required}}@Nullable {{/required}}{{/lambda.jSpecifyNullable}}{{#lambda.jSpecifyDatatype}}{{{datatypeWithEnum}}}{{/lambda.jSpecifyDatatype}}{{/useJspecify}}{{^useJspecify}}{{>nullableAnnotation}}{{{datatypeWithEnum}}}{{/useJspecify}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) {
{{#parent}}
super({{#parentVars}}{{name}}{{^-last}}, {{/-last}}{{/parentVars}});
{{/parent}}
Expand All @@ -153,7 +153,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
{{^lombok.Data}}

{{! begin feature: fluent setter methods }}
public {{classname}} {{name}}({{>nullableAnnotation}}{{{datatypeWithEnum}}} {{name}}) {
public {{classname}} {{name}}({{#useJspecify}}{{#lambda.jSpecifyNullable}}{{^required}}{{^useOptional}}@Nullable {{/useOptional}}{{#useOptional}}{{#optionalAcceptNullable}}@Nullable {{/optionalAcceptNullable}}{{/useOptional}}{{/required}}{{/lambda.jSpecifyNullable}}{{#lambda.jSpecifyDatatype}}{{{datatypeWithEnum}}}{{/lambda.jSpecifyDatatype}}{{/useJspecify}}{{^useJspecify}}{{>nullableAnnotation_default}}{{{datatypeWithEnum}}}{{/useJspecify}} {{name}}) {
{{#openApiNullable}}
this.{{name}} = {{#isNullable}}JsonNullable.of({{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional.of{{#optionalAcceptNullable}}Nullable{{/optionalAcceptNullable}}({{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{name}}{{#isNullable}}){{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}){{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}};
{{/openApiNullable}}
Expand Down Expand Up @@ -348,7 +348,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString({{>nullableAnnotation}}Object o) {
private String toIndentedString(@Nullable Object o) {
return o == null ? "null" : o.toString().replace("\n", "\n ");
}
{{/lombok.ToString}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4654,7 +4654,9 @@ public void testJspecify(String library, boolean useSpringBoot4, boolean hasJspe
final Map<String, File> files = generateFromContract("src/test/resources/3_0/java/jspecify.yaml", library,
Map.of(USE_JSPECIFY, true,
"containerDefaultToNull", true,
USE_SPRING_BOOT4, useSpringBoot4
USE_SPRING_BOOT4, useSpringBoot4,
JavaClientCodegen.OPENAPI_NULLABLE, false,
GENERATE_CONSTRUCTOR_WITH_ALL_ARGS, true
),
codegenConfigurator ->
codegenConfigurator
Expand Down Expand Up @@ -4683,12 +4685,18 @@ public void testJspecify(String library, boolean useSpringBoot4, boolean hasJspe
"import org.jspecify.annotations.Nullable;",
"private java.time.@Nullable Instant dt;",
"setDt(java.time.@Nullable Instant dt)",
"dt(java.time.@Nullable Instant dt)",
"Foo dt(java.time.@Nullable Instant dt)",
"setBinary(@Nullable File binary)",
"public @Nullable File getBinary()",
"List<java.time.@Nullable Instant> getListOfDt()",
"setListOfDt(List<java.time.@Nullable Instant> listOfDt)"
);
"@Nullable List<java.time.Instant> getListOfDt()",
"private java.time.@Nullable Instant nullableDt;",
"private @Nullable List<java.time.Instant> listOfDt;",
"setListOfDt(@Nullable List<java.time.Instant> listOfDt)");
if (!NATIVE.equals(library)) { // native library does not generate all arg constructors
JavaFileAssert.assertThat(files.get("Foo.java"))
.fileContains(
"public Foo(@JsonProperty(JSON_PROPERTY_DT) java.time.@Nullable Instant dt, @JsonProperty(JSON_PROPERTY_NULLABLE_DT) java.time.@Nullable Instant nullableDt, @JsonProperty(JSON_PROPERTY_BINARY) @Nullable File binary, @JsonProperty(JSON_PROPERTY_NULLABLE_BINARY) @Nullable File nullableBinary, @JsonProperty(JSON_PROPERTY_LIST_OF_DT) @Nullable List<java.time.Instant> listOfDt, @JsonProperty(JSON_PROPERTY_LIST_MIN_INTEMS) @Nullable List<java.time.Instant> listMinIntems, @JsonProperty(JSON_PROPERTY_NULLABLE_LIST_MIN_INTEMS) @Nullable List<java.time.Instant> nullableListMinIntems, @JsonProperty(JSON_PROPERTY_REQUIRED_DT) java.time.Instant requiredDt, @JsonProperty(JSON_PROPERTY_NUMBER) java.math.@Nullable BigDecimal number, @JsonProperty(JSON_PROPERTY_NULLABLE_NUMBER) java.math.@Nullable BigDecimal nullableNumber, @JsonProperty(JSON_PROPERTY_COLOR) @Nullable String color, @JsonProperty(JSON_PROPERTY_REQUIRED_COLOR) String requiredColor, @JsonProperty(JSON_PROPERTY_NULLABLE_COLOR) @Nullable String nullableColor) {");
}
if (!RESTTEMPLATE.equals(library)) {
JavaFileAssert.assertThat(files.get("DefaultApi.java"))
.fileContains(
Expand All @@ -4702,7 +4710,70 @@ public void testJspecify(String library, boolean useSpringBoot4, boolean hasJspe
.fileContains("@org.jspecify.annotations.NullMarked");
JavaFileAssert.assertThat(files.get("client/package-info.java"))
.fileContains("@org.jspecify.annotations.NullMarked");
}

@Test(dataProvider = "jspecifyLibraries")
public void testJspecify_openapiNullable(String library, boolean useSpringBoot4, boolean hasJspecifyDependency) throws IOException {
final Map<String, File> files = generateFromContract("src/test/resources/3_0/java/jspecify.yaml", library,
Map.of(USE_JSPECIFY, true,
"containerDefaultToNull", true,
USE_SPRING_BOOT4, useSpringBoot4,
JavaClientCodegen.OPENAPI_NULLABLE, true,
GENERATE_CONSTRUCTOR_WITH_ALL_ARGS, true
),
codegenConfigurator ->
codegenConfigurator
.setValidateSpec(false)
.addTypeMapping("OffsetDateTime", "java.time.Instant")
.addTypeMapping("BigDecimal", "java.math.BigDecimal"));

if (hasJspecifyDependency) {
assertThat(files.get("build.gradle")).content()
.contains("implementation \"org.jspecify:jspecify:1.0.0\"")
.doesNotContain("findbugs");
assertThat(files.get("pom.xml")).content()
.contains(
"<groupId>org.jspecify</groupId>",
"<artifactId>jspecify</artifactId>",
"<version>1.0.0</version>")
.doesNotContain("findbugs");
} else {
assertThat(files.get("build.gradle")).content()
.doesNotContain("org.jspecify");
assertThat(files.get("pom.xml")).content()
.doesNotContain("org.jspecify");
}
JavaFileAssert.assertThat(files.get("Foo.java"))
.fileContains(
"import org.jspecify.annotations.Nullable;",
"private java.time.@Nullable Instant dt;",
"setDt(java.time.@Nullable Instant dt)",
"Foo dt(java.time.@Nullable Instant dt)",
"setBinary(@Nullable File binary)",
"public @Nullable File getBinary()",
"@Nullable List<java.time.Instant> getListOfDt()",
"setListOfDt(@Nullable List<java.time.Instant> listOfDt)",
"JsonNullable<java.time.Instant> nullableDt = JsonNullable.<java.time.Instant>undefined()",
"private JsonNullable<File> nullableBinary = JsonNullable.<File>undefined();");
if (!NATIVE.equals(library)) { // native library does not generate all arg constructors
JavaFileAssert.assertThat(files.get("Foo.java"))
.fileContains(
"public Foo(@JsonProperty(JSON_PROPERTY_DT) java.time.@Nullable Instant dt, @JsonProperty(JSON_PROPERTY_NULLABLE_DT) java.time.@Nullable Instant nullableDt, @JsonProperty(JSON_PROPERTY_BINARY) @Nullable File binary, @JsonProperty(JSON_PROPERTY_NULLABLE_BINARY) @Nullable File nullableBinary, @JsonProperty(JSON_PROPERTY_LIST_OF_DT) @Nullable List<java.time.Instant> listOfDt, @JsonProperty(JSON_PROPERTY_LIST_MIN_INTEMS) @Nullable List<java.time.Instant> listMinIntems, @JsonProperty(JSON_PROPERTY_NULLABLE_LIST_MIN_INTEMS) @Nullable List<java.time.Instant> nullableListMinIntems, @JsonProperty(JSON_PROPERTY_REQUIRED_DT) java.time.Instant requiredDt, @JsonProperty(JSON_PROPERTY_NUMBER) java.math.@Nullable BigDecimal number, @JsonProperty(JSON_PROPERTY_NULLABLE_NUMBER) java.math.@Nullable BigDecimal nullableNumber, @JsonProperty(JSON_PROPERTY_COLOR) @Nullable String color, @JsonProperty(JSON_PROPERTY_REQUIRED_COLOR) String requiredColor, @JsonProperty(JSON_PROPERTY_NULLABLE_COLOR) @Nullable String nullableColor) {"
);
}
if (!RESTTEMPLATE.equals(library)) {
JavaFileAssert.assertThat(files.get("DefaultApi.java"))
.fileContains(
"import org.jspecify.annotations.Nullable;",
"(java.time.@Nullable Instant dtParam, java.time.@Nullable Instant dtQuery, java.time.@Nullable Instant dtCookie)"
);
}
JavaFileAssert.assertThat(files.get("api/package-info.java"))
.fileContains("@org.jspecify.annotations.NullMarked");
JavaFileAssert.assertThat(files.get("model/package-info.java"))
.fileContains("@org.jspecify.annotations.NullMarked");
JavaFileAssert.assertThat(files.get("client/package-info.java"))
.fileContains("@org.jspecify.annotations.NullMarked");
}

@DataProvider(name = "replaceOneOf")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ public JavaFileAssert fileContains(final String... lines) {
Assertions.assertThat(actualBody)
.withFailMessage(
"File should contain lines\n====\n%s\n====\nbut actually was\n====\n%s\n====",
Arrays.stream(lines).collect(Collectors.joining(System.lineSeparator())), actualBody
Arrays.stream(lines)
.filter(line -> !actualBody.contains(line))
.collect(Collectors.joining(System.lineSeparator())), actualBody
)
.contains(lines);

Expand All @@ -249,7 +251,9 @@ public JavaFileAssert fileDoesNotContain(final String... lines) {
Assertions.assertThat(actualBody)
.withFailMessage(
"File should not contain lines\n====\n%s\n====\nbut actually was\n====\n%s\n====",
Arrays.stream(lines).collect(Collectors.joining(System.lineSeparator())), actualBody
Arrays.stream(lines)
.filter(line -> actualBody.contains(line))
.collect(Collectors.joining(System.lineSeparator())), actualBody
)
.doesNotContain(lines);

Expand Down
Loading
Loading