-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
[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
Changes from 30 commits
7bd73f8
603af01
7521f10
1cee79c
9af4802
4aae7ce
8023714
7c4b50b
cca147d
c40a345
fffe2e9
96d0f36
4f2fdbc
b1148fb
b4a4a66
f8e0065
ad2359a
b2f8918
8f66a71
1540fcb
cf6b820
bde60be
22abaf7
25cfde3
385c2d9
83e040b
128beb1
d2faaac
15f2b5d
25b782a
6050184
c940aef
fbc576d
ccf5c09
9e98c52
2c4bfd4
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() { | ||
|
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. no need to set the JAVAX_PACKAGE to jspecify?
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. Sorry, obsolete commit, I've removed it @Chrimle thanks for the review
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. Okay, so there's no need for something like Resolve if no concern 👍 |
||
| importMapping.put("Nullable", "org.jspecify.annotations.Nullable"); | ||
|
Member
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. please add a docstring to this new function explaining what it goes. |
||
| 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.
as usual, what about adding the new sample folder to the github workflow(s)?