-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
[java] [Spring] useJspecify for java clients and spring generator #23256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
7bd73f8
603af01
7521f10
1cee79c
9af4802
4aae7ce
8023714
7c4b50b
cca147d
c40a345
fffe2e9
96d0f36
4f2fdbc
b1148fb
b4a4a66
f8e0065
ad2359a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| generatorName: java | ||
| outputDir: samples/client/petstore/java/native-jackson3-jspecify | ||
| library: native | ||
| inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml | ||
| templateDir: modules/openapi-generator/src/main/resources/Java | ||
| validateSpec: false | ||
| additionalProperties: | ||
| artifactId: petstore-native-jackson3 | ||
| hideGenerationTimestamp: "true" | ||
| generateBuilders: true | ||
| useReflectionEqualsHashCode: "true" | ||
| useJackson3: "true" | ||
| openApiNullable: "false" | ||
| useJspecify: true | ||
| typeMappings: | ||
| OffsetDateTime: java.time.Instant | ||
| BigDecimal: java.math.BigDecimal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| generatorName: java | ||
| outputDir: samples/client/petstore/java/restclient-springBoot4-jackson3-jspecify | ||
| library: restclient | ||
| inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml | ||
| templateDir: modules/openapi-generator/src/main/resources/Java | ||
| validateSpec: false | ||
| additionalProperties: | ||
| artifactId: petstore-restclient | ||
| hideGenerationTimestamp: "true" | ||
| containerDefaultToNull: "true" | ||
| useSpringBoot4: true | ||
| useJackson3: true | ||
| openApiNullable: false | ||
| useJspecify: true | ||
| typeMappings: | ||
| OffsetDateTime: java.time.Instant | ||
| BigDecimal: java.math.BigDecimal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| generatorName: java | ||
| outputDir: samples/client/petstore/java/resttemplate-springBoot4-jackson3-jspecify | ||
| library: resttemplate | ||
| inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml | ||
| templateDir: modules/openapi-generator/src/main/resources/Java | ||
| validateSpec: false | ||
| additionalProperties: | ||
| artifactId: petstore-resttemplate | ||
| hideGenerationTimestamp: "true" | ||
| containerDefaultToNull: "true" | ||
| useJakartaEe: true | ||
| useSpringBoot4: true | ||
| useJackson3: true | ||
| openApiNullable: false | ||
| useJspecify: true | ||
| typeMappings: | ||
| OffsetDateTime: java.time.Instant | ||
| BigDecimal: java.math.BigDecimal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| generatorName: java | ||
| outputDir: samples/client/petstore/java/webclient-springBoot4-jackson3-jspecify | ||
| library: webclient | ||
| inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml | ||
| templateDir: modules/openapi-generator/src/main/resources/Java | ||
| validateSpec: false | ||
| additionalProperties: | ||
| artifactId: petstore-webclient | ||
| hideGenerationTimestamp: "true" | ||
| containerDefaultToNull: "true" | ||
| useSpringBoot4: true | ||
| useJackson3: true | ||
| openApiNullable: false | ||
| useJspecify: true | ||
| typeMappings: | ||
| OffsetDateTime: java.time.Instant | ||
| BigDecimal: java.math.BigDecimal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| generatorName: spring | ||
| library: spring-boot | ||
| outputDir: samples/openapi3/server/petstore/springboot-4-jspecify | ||
| 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: false | ||
| useJspecify: true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,8 +22,10 @@ | |
| import com.fasterxml.jackson.databind.node.ArrayNode; | ||
| import com.fasterxml.jackson.databind.node.ObjectNode; | ||
| import com.google.common.base.Strings; | ||
| import com.google.common.collect.ImmutableMap; | ||
| import com.google.common.collect.Sets; | ||
| import com.samskivert.mustache.Mustache; | ||
| import com.samskivert.mustache.Template; | ||
| import io.swagger.v3.oas.models.OpenAPI; | ||
| import io.swagger.v3.oas.models.Operation; | ||
| import io.swagger.v3.oas.models.PathItem; | ||
|
|
@@ -58,6 +60,8 @@ | |
| import javax.lang.model.SourceVersion; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.Writer; | ||
| import java.time.LocalDate; | ||
| import java.time.ZoneId; | ||
| import java.time.ZonedDateTime; | ||
|
|
@@ -104,6 +108,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code | |
| public static final String IMPLICIT_HEADERS_REGEX = "implicitHeadersRegex"; | ||
| public static final String JAVAX_PACKAGE = "javaxPackage"; | ||
| public static final String USE_JAKARTA_EE = "useJakartaEe"; | ||
| public static final String USE_JSPECIFY = "useJspecify"; | ||
| public static final String CONTAINER_DEFAULT_TO_NULL = "containerDefaultToNull"; | ||
| public static final String DISABLE_DISCRIMINATOR_JSON_IGNORE_PROPERTIES = "disableDiscriminatorJsonIgnoreProperties"; | ||
|
|
||
|
|
@@ -216,6 +221,11 @@ protected enum ENUM_PROPERTY_NAMING_TYPE {MACRO_CASE, legacy, original} | |
| */ | ||
| @Getter @Setter | ||
| protected boolean useBeanValidation = false; | ||
| @Getter | ||
| @Setter | ||
| protected boolean useJspecify; | ||
| protected JSpecifyNullableLambda jSpecifyNullableLambda; | ||
|
|
||
| private Map<String, String> schemaKeyToModelNameCache = new HashMap<>(); | ||
|
|
||
| public AbstractJavaCodegen() { | ||
|
|
@@ -597,6 +607,7 @@ public void processOpts() { | |
| convertPropertyToBooleanAndWriteBack(CAMEL_CASE_DOLLAR_SIGN, this::setCamelCaseDollarSign); | ||
| convertPropertyToBooleanAndWriteBack(USE_ONE_OF_INTERFACES, this::setUseOneOfInterfaces); | ||
| convertPropertyToStringAndWriteBack(CodegenConstants.ENUM_PROPERTY_NAMING, this::setEnumPropertyNaming); | ||
| convertPropertyToBooleanAndWriteBack(USE_JSPECIFY, this::setUseJspecify); | ||
|
|
||
| if (!StringUtils.isEmpty(parentGroupId) && !StringUtils.isEmpty(parentArtifactId) && !StringUtils.isEmpty(parentVersion)) { | ||
| additionalProperties.put("parentOverridden", true); | ||
|
|
@@ -847,6 +858,20 @@ protected void applyJakartaPackage() { | |
| writePropertyBack(JAVAX_PACKAGE, "jakarta"); | ||
| } | ||
|
|
||
| protected void applyJspecify() { | ||
| importMapping.put("Nullable", "org.jspecify.annotations.Nullable"); | ||
| if (Boolean.TRUE.equals(additionalProperties.get(CodegenConstants.GENERATE_MODELS))) { | ||
| supportingFiles.add(new SupportingFile("modelPackageInfo.mustache", | ||
| (sourceFolder + File.separator + modelPackage).replace(".", java.io.File.separator), | ||
| "package-info.java")); | ||
| } | ||
| if (Boolean.TRUE.equals(additionalProperties.get(CodegenConstants.GENERATE_APIS))) { | ||
| supportingFiles.add(new SupportingFile("apiPackageInfo.mustache", | ||
| (sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), | ||
| "package-info.java")); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public String escapeReservedWord(String name) { | ||
| if (this.reservedWordsMappings().containsKey(name)) { | ||
|
|
@@ -2653,4 +2678,78 @@ public void setEnumPropertyNaming(final String enumPropertyNamingType) { | |
| throw new RuntimeException(sb.toString()); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() { | ||
| this.jSpecifyNullableLambda = new JSpecifyNullableLambda(); | ||
| // Add jSpecify nullable annotation in the correct location before or inside a declartion | ||
| Mustache.Lambda jSpecifyDatatypeLambda = (fragment, writer) -> { | ||
| String dataType = fragment.execute(); | ||
| if (jSpecifyNullableLambda.keptNullable) { | ||
| jSpecifyNullableLambda.keptNullable = false; | ||
| int idx = dataType.lastIndexOf('.'); | ||
| if (idx > 0) { | ||
| // generate declaration like java.time.@Nullable Timestamp | ||
| writer.write(dataType.substring(0, idx + 1)); | ||
| writer.write("@Nullable "); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The trailing space in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want the space between I've found a possible improvement in the java pojo.mustache templates.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. aha, nice find! I just had a gut feeling that "
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be fair, there's a LOT of formatting issues in pretty much all generators. I use mustache-comments quite a lot to make it structured/readable, but also keeping the formatting. example: so, if it's an easy fix, why not 😉
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... but if it's not - don't bother with it right now 😉 |
||
| writer.write(dataType.substring(idx + 1)); | ||
| } else { | ||
| if (dataType.startsWith(" ")) { | ||
| writer.write(" @Nullable"); | ||
| writer.write(dataType); | ||
| } else { | ||
| writer.write("@Nullable "); | ||
| writer.write(dataType); | ||
| } | ||
| } | ||
| } else { | ||
| writer.write(dataType); | ||
| } | ||
| }; | ||
| return super.addMustacheLambdas() | ||
| .put("jSpecifyDatatype", jSpecifyDatatypeLambda) | ||
| .put("jSpecifyNullable", jSpecifyNullableLambda); | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * for Jspecify, remove @Nullable before the datatype and set keptNullable to true if done. | ||
| */ | ||
| class JSpecifyNullableLambda implements Mustache.Lambda { | ||
| private String nullableAnnotation = "@Nullable"; | ||
| // remember if @Nullable is needed | ||
| boolean keptNullable = false; | ||
|
|
||
| public void setNullableAnnotation(String nullableAnnotation) { | ||
| this.nullableAnnotation = nullableAnnotation; | ||
| } | ||
|
|
||
| @Override | ||
| public void execute(Template.Fragment fragment, Writer writer) throws IOException { | ||
| keptNullable = false; | ||
| String value = fragment.execute(); | ||
| if (useJspecify) { | ||
| if (value.startsWith(nullableAnnotation)) { | ||
| keptNullable = true; | ||
| int idx = nullableAnnotation.length(); | ||
| // trim left | ||
| while (idx < value.length() && value.charAt(idx) == ' ') { | ||
| idx ++; | ||
| } | ||
| value = value.substring(idx); | ||
| } | ||
| } | ||
| writer.write(value); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Adds Nullable import if any parameter is nullable or optional. | ||
| */ | ||
| protected void addNullableImportForOperation(CodegenOperation codegenOperation) { | ||
| codegenOperation.allParams.stream() | ||
| .filter(CodegenParameter::notRequiredOrIsNullable) | ||
| .findAny() | ||
| .ifPresent(param -> codegenOperation.imports.add("Nullable")); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to set the JAVAX_PACKAGE to jspecify?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, obsolete commit, I've removed it
@Chrimle thanks for the review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so there's no need for something like
writePropertyBack(JAVAX_PACKAGE, "jspecify")?Resolve if no concern 👍