[Micronaut] Improving micronaut-model and micronaut-client generation (#14065)

* fix documentation

* improve build.gradle.mustache and pom.xml.mustache to assume different serialization libs jackson or micronaut-serde-jackson

* improve pojo.mustache to skip generating @JsonDeserialize as for micronaut-serde-jackson

* improve model generating by removing visible flag from @JsonTypeInfo as it is not supported by micronaut-serde-jackson

Co-authored-by: dmitry.kubakhov <dmitry.kubakhov@check24.de>
This commit is contained in:
DmitryKubahov 2022-11-25 08:28:18 +01:00 committed by GitHub
parent 90a8b4effb
commit f9d4d28f48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 188 additions and 7 deletions

View File

@ -18,6 +18,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|additionalClientTypeAnnotations|Additional annotations for client type(class level annotations). List separated by semicolon(;) or new line (Linux or Windows)| |null|
|additionalEnumTypeAnnotations|Additional annotations for enum type(class level annotations)| |null|
|additionalModelTypeAnnotations|Additional annotations for model type(class level annotations). List separated by semicolon(;) or new line (Linux or Windows)| |null|
|additionalOneOfTypeAnnotations|Additional annotations for oneOf interfaces(class level annotations). List separated by semicolon(;) or new line (Linux or Windows)| |null|
@ -70,6 +71,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|scmDeveloperConnection|SCM developer connection in generated pom.xml| |scm:git:git@github.com:openapitools/openapi-generator.git|
|scmUrl|SCM URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
|serializableModel|boolean - toggle &quot;implements Serializable&quot; for generated models| |false|
|serializationLibrary|Serialization library for model|<dl><dt>**jackson**</dt><dd>Jackson as serialization library</dd><dt>**micronaut_serde_jackson**</dt><dd>Use micronaut-serialization with Jackson annotations</dd></dl>|jackson|
|snapshotVersion|Uses a SNAPSHOT version.|<dl><dt>**true**</dt><dd>Use a SnapShot Version</dd><dt>**false**</dt><dd>Use a Release Version</dd></dl>|null|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|

View File

@ -72,6 +72,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|scmDeveloperConnection|SCM developer connection in generated pom.xml| |scm:git:git@github.com:openapitools/openapi-generator.git|
|scmUrl|SCM URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
|serializableModel|boolean - toggle &quot;implements Serializable&quot; for generated models| |false|
|serializationLibrary|Serialization library for model|<dl><dt>**jackson**</dt><dd>Jackson as serialization library</dd><dt>**micronaut_serde_jackson**</dt><dd>Use micronaut-serialization with Jackson annotations</dd></dl>|jackson|
|snapshotVersion|Uses a SNAPSHOT version.|<dl><dt>**true**</dt><dd>Use a SnapShot Version</dd><dt>**false**</dt><dd>Use a Release Version</dd></dl>|null|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|

View File

@ -53,6 +53,7 @@ public abstract class JavaMicronautAbstractCodegen extends AbstractJavaCodegen i
public static final String OPT_GENERATE_SWAGGER_ANNOTATIONS_TRUE = "true";
public static final String OPT_GENERATE_SWAGGER_ANNOTATIONS_FALSE = "false";
public static final String OPT_GENERATE_OPERATION_ONLY_FOR_FIRST_TAG = "generateOperationOnlyForFirstTag";
public enum SERIALIZATION_LIBRARY_TYPE {jackson, micronaut_serde_jackson}
protected final Logger LOGGER = LoggerFactory.getLogger(JavaMicronautAbstractCodegen.class);
@ -69,6 +70,7 @@ public abstract class JavaMicronautAbstractCodegen extends AbstractJavaCodegen i
protected String appName;
protected String generateSwaggerAnnotations;
protected boolean generateOperationOnlyForFirstTag;
protected String serializationLibrary = SERIALIZATION_LIBRARY_TYPE.jackson.name();
public static final String CONTENT_TYPE_APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json";
@ -123,7 +125,6 @@ public abstract class JavaMicronautAbstractCodegen extends AbstractJavaCodegen i
);
// Set additional properties
additionalProperties.put("jackson", "true");
additionalProperties.put("openbrace", "{");
additionalProperties.put("closebrace", "}");
@ -180,6 +181,14 @@ public abstract class JavaMicronautAbstractCodegen extends AbstractJavaCodegen i
opt.setEnum(valuesEnum);
});
final CliOption serializationLibraryOpt = CliOption.newString(CodegenConstants.SERIALIZATION_LIBRARY, "Serialization library for model");
serializationLibraryOpt.defaultValue(SERIALIZATION_LIBRARY_TYPE.jackson.name());
Map<String, String> serializationLibraryOptions = new HashMap<>();
serializationLibraryOptions.put(SERIALIZATION_LIBRARY_TYPE.jackson.name(), "Jackson as serialization library");
serializationLibraryOptions.put(SERIALIZATION_LIBRARY_TYPE.micronaut_serde_jackson.name(), "Use micronaut-serialization with Jackson annotations");
serializationLibraryOpt.setEnum(serializationLibraryOptions);
cliOptions.add(serializationLibraryOpt);
// Add reserved words
String[] reservedWordsArray = {
"client", "format", "queryvalue", "queryparam", "pathvariable", "header", "cookie",
@ -305,6 +314,11 @@ public abstract class JavaMicronautAbstractCodegen extends AbstractJavaCodegen i
additionalProperties.put("generateSwagger2Annotations", true);
}
if (additionalProperties.containsKey(CodegenConstants.SERIALIZATION_LIBRARY)) {
setSerializationLibrary((String) additionalProperties.get(CodegenConstants.SERIALIZATION_LIBRARY));
}
additionalProperties.put(this.serializationLibrary, true);
// Add all the supporting files
String resourceFolder = projectFolder + "/resources";
supportingFiles.add(new SupportingFile("common/configuration/application.yml.mustache", resourceFolder, "application.yml").doNotOverwrite());
@ -710,4 +724,16 @@ public abstract class JavaMicronautAbstractCodegen extends AbstractJavaCodegen i
writer.write(fragment.execute().replace('.', '_'));
}
}
public void setSerializationLibrary(final String serializationLibrary) {
try {
this.serializationLibrary = JavaMicronautAbstractCodegen.SERIALIZATION_LIBRARY_TYPE.valueOf(serializationLibrary).name();
} catch (IllegalArgumentException ex) {
StringBuilder sb = new StringBuilder(serializationLibrary + " is an invalid enum property naming option. Please choose from:");
for (JavaMicronautAbstractCodegen.SERIALIZATION_LIBRARY_TYPE availableSerializationLibrary : JavaMicronautAbstractCodegen.SERIALIZATION_LIBRARY_TYPE.values()) {
sb.append("\n ").append(availableSerializationLibrary.name());
}
throw new RuntimeException(sb.toString());
}
}
}

View File

@ -1,5 +1,8 @@
package org.openapitools.codegen.languages;
import java.util.Arrays;
import java.util.List;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
@ -10,10 +13,12 @@ import org.openapitools.codegen.meta.Stability;
public class JavaMicronautClientCodegen extends JavaMicronautAbstractCodegen {
public static final String OPT_CONFIGURE_AUTH = "configureAuth";
public static final String ADDITIONAL_CLIENT_TYPE_ANNOTATIONS = "additionalClientTypeAnnotations";
public static final String NAME = "java-micronaut-client";
protected boolean configureAuthorization;
protected List<String> additionalClientTypeAnnotations;
public JavaMicronautClientCodegen() {
super();
@ -27,6 +32,7 @@ public class JavaMicronautClientCodegen extends JavaMicronautAbstractCodegen {
additionalProperties.put("client", "true");
cliOptions.add(CliOption.newBoolean(OPT_CONFIGURE_AUTH, "Configure all the authorization methods as specified in the file", configureAuthorization));
cliOptions.add(CliOption.newString(ADDITIONAL_CLIENT_TYPE_ANNOTATIONS, "Additional annotations for client type(class level annotations). List separated by semicolon(;) or new line (Linux or Windows)"));
}
@Override
@ -75,6 +81,12 @@ public class JavaMicronautClientCodegen extends JavaMicronautAbstractCodegen {
supportingFiles.add(new SupportingFile("client/auth/configuration/HttpBasicAuthConfiguration.mustache", authConfigurationFolder, "HttpBasicAuthConfiguration.java"));
}
if (additionalProperties.containsKey(ADDITIONAL_CLIENT_TYPE_ANNOTATIONS)) {
String additionalClientAnnotationsList = additionalProperties.get(ADDITIONAL_CLIENT_TYPE_ANNOTATIONS).toString();
this.setAdditionalClientTypeAnnotations(Arrays.asList(additionalClientAnnotationsList.trim().split("\\s*(;|\\r?\\n)\\s*")));
additionalProperties.put(ADDITIONAL_CLIENT_TYPE_ANNOTATIONS, additionalClientTypeAnnotations);
}
// Api file
apiTemplateFiles.clear();
apiTemplateFiles.put("client/api.mustache", ".java");
@ -93,4 +105,8 @@ public class JavaMicronautClientCodegen extends JavaMicronautAbstractCodegen {
apiDocTemplateFiles.clear();
apiDocTemplateFiles.put("client/doc/api_doc.mustache", ".md");
}
public void setAdditionalClientTypeAnnotations(final List<String> additionalClientTypeAnnotations) {
this.additionalClientTypeAnnotations = additionalClientTypeAnnotations;
}
}

View File

@ -41,6 +41,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
{{/generateSwagger2Annotations}}
{{#additionalClientTypeAnnotations}}
{{{.}}}
{{/additionalClientTypeAnnotations}}
{{>common/generatedAnnotation}}
@Client("${{openbrace}}{{{applicationName}}}-base-path{{closebrace}}")
public interface {{classname}} {

View File

@ -33,6 +33,10 @@ dependencies {
{{#useAuth}}
annotationProcessor("io.micronaut.security:micronaut-security-annotations")
{{/useAuth}}
{{#micronaut_serde_jackson}}
annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
implementation("io.micronaut.serde:micronaut-serde-jackson")
{{/micronaut_serde_jackson}}
implementation("io.micronaut:micronaut-http-client")
implementation("io.micronaut:micronaut-runtime")
implementation("io.micronaut:micronaut-validation")
@ -51,6 +55,15 @@ dependencies {
{{/generateSwagger2Annotations}}
runtimeOnly("ch.qos.logback:logback-classic")
}
{{#micronaut_serde_jackson}}
// TODO Please, check the version of the serde, maybe it must be upgraded.
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(module("io.micronaut:micronaut-jackson-databind"))
.using(module("io.micronaut.serde:micronaut-serde-jackson:1.3.3"))
}
}
{{/micronaut_serde_jackson}}
// TODO Set the main class
application {

View File

@ -141,6 +141,24 @@
<version>${swagger-annotations-version}</version>
</dependency>
{{/generateSwagger2Annotations}}
{{#micronaut_serde_jackson}}
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-runtime</artifactId>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.micronaut.serde</groupId>
<artifactId>micronaut-serde-jackson</artifactId>
<scope>compile</scope>
</dependency>
{{/micronaut_serde_jackson}}
</dependencies>
<build>
@ -170,6 +188,13 @@
<version>${micronaut.security.version}</version>
</path>
{{/useAuth}}
{{#micronaut_serde_jackson}}
<path>
<groupId>io.micronaut.serde</groupId>
<artifactId>micronaut-serde-processor</artifactId>
<version>${micronaut.serialization.version}</version>
</path>
{{/micronaut_serde_jackson}}
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amicronaut.processing.group={{groupId}}</arg>

View File

@ -18,9 +18,19 @@
{{/isXmlWrapped}}
{{/isContainer}}
{{/withXml}}
{{#isDateTime}}
{{#jackson}}
{{#isDateTime}}
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "{{{datetimeFormat}}}")
{{/isDateTime}}
{{#isDate}}
{{/isDateTime}}
{{#isDate}}
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "{{{dateFormat}}}")
{{/isDate}}
{{/isDate}}
{{/jackson}}
{{#micronaut_serde_jackson}}
{{#isDateTime}}
@JsonFormat(pattern = "{{{datetimeFormat}}}")
{{/isDateTime}}
{{#isDate}}
@JsonFormat(pattern = "{{{dateFormat}}}")
{{/isDate}}
{{/micronaut_serde_jackson}}

View File

@ -9,6 +9,9 @@
@Schema({{#name}}name = "{{name}}", {{/name}}description = "{{{description}}}")
{{/generateSwagger2Annotations}}
{{/description}}
{{#micronaut_serde_jackson}}
@io.micronaut.serde.annotation.Serdeable
{{/micronaut_serde_jackson}}
{{#jackson}}
@JsonPropertyOrder({
{{#vars}}
@ -236,8 +239,8 @@ Declare the class with extends and implements
{{^isReadOnly}}
{{#jackson}}
{{^vendorExtensions.x-is-jackson-optional-nullable}}
{{>common/model/jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}}{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}}
{{/vendorExtensions.x-setter-extra-annotation}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) {
{{>common/model/jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}}
{{/vendorExtensions.x-setter-extra-annotation}}{{/jackson}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) {
{{#vendorExtensions.x-is-jackson-optional-nullable}}
this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});
{{/vendorExtensions.x-is-jackson-optional-nullable}}

View File

@ -15,3 +15,20 @@
{{/-last}}
{{/discriminator.mappedModels}}
{{/jackson}}
{{#micronaut_serde_jackson}}
@JsonIgnoreProperties(
value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}")
{{#discriminator.mappedModels}}
{{#-first}}
@JsonSubTypes({
{{/-first}}
@JsonSubTypes.Type(value = {{modelName}}.class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"),
{{#-last}}
})
{{/-last}}
{{/discriminator.mappedModels}}
{{/micronaut_serde_jackson}}

View File

@ -0,0 +1,53 @@
package org.openapitools.codegen.java.micronaut;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.languages.JavaMicronautAbstractCodegen;
import org.openapitools.codegen.languages.JavaMicronautClientCodegen;
import org.testng.annotations.Test;
public class JavaMicronautClientCodegenSerializationLibraryTest extends AbstractMicronautCodegenTest {
@Test
public void testSerializationLibraryJackson() {
JavaMicronautClientCodegen codegen = new JavaMicronautClientCodegen();
codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, JavaMicronautAbstractCodegen.SERIALIZATION_LIBRARY_TYPE.jackson.name());
String outputPath = generateFiles(codegen, PETSTORE_PATH,
CodegenConstants.MODELS);
// Model does not contain micronaut serde annotation
String micronautSerDeAnnotation = "@io.micronaut.serde.annotation.Serdeable";
String modelPath = outputPath + "src/main/java/org/openapitools/model/";
assertFileNotContains(modelPath + "Pet.java", micronautSerDeAnnotation);
assertFileNotContains(modelPath + "User.java", micronautSerDeAnnotation);
assertFileNotContains(modelPath + "Order.java", micronautSerDeAnnotation);
assertFileNotContains(modelPath + "Tag.java", micronautSerDeAnnotation);
assertFileNotContains(modelPath + "Category.java", micronautSerDeAnnotation);
//JsonFormat with jackson must be with shape attribute
assertFileContains(modelPath + "Order.java", "@JsonFormat(shape = JsonFormat.Shape.STRING");
}
/**
* Checks micronaut-serde-jackson limitation.
* @see <a href="https://micronaut-projects.github.io/micronaut-serialization/latest/guide/index.html#jacksonAnnotations"></a>
*/
@Test
public void testSerializationLibraryMicronautSerdeJackson() {
JavaMicronautClientCodegen codegen = new JavaMicronautClientCodegen();
codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, JavaMicronautAbstractCodegen.SERIALIZATION_LIBRARY_TYPE.micronaut_serde_jackson.name());
String outputPath = generateFiles(codegen, PETSTORE_PATH,
CodegenConstants.MODELS);
// Model contains micronaut serde annotation
String micronautSerDeAnnotation = "@io.micronaut.serde.annotation.Serdeable";
String modelPath = outputPath + "src/main/java/org/openapitools/model/";
assertFileContains(modelPath + "Pet.java", micronautSerDeAnnotation);
assertFileContains(modelPath + "User.java", micronautSerDeAnnotation);
assertFileContains(modelPath + "Order.java", micronautSerDeAnnotation);
assertFileContains(modelPath + "Tag.java", micronautSerDeAnnotation);
assertFileContains(modelPath + "Category.java", micronautSerDeAnnotation);
//JsonFormat with micronaut-serde-jackson must be without shape attribute
assertFileNotContains(modelPath + "Order.java", "@JsonFormat(shape = JsonFormat.Shape.STRING");
}
}

View File

@ -239,4 +239,16 @@ public class MicronautClientCodegenTest extends AbstractMicronautCodegenTest {
String resourcesPath = outputPath + "src/main/resources/";
assertFileContains(resourcesPath + "application.yml", "OAuth_2_0_Client_Credentials:");
}
@Test
public void testAdditionalClientTypeAnnotations() {
JavaMicronautClientCodegen codegen = new JavaMicronautClientCodegen();
codegen.additionalProperties().put(JavaMicronautClientCodegen.ADDITIONAL_CLIENT_TYPE_ANNOTATIONS, "MyAdditionalAnnotation1(1,${param1});MyAdditionalAnnotation2(2,${param2});");
String outputPath = generateFiles(codegen, PETSTORE_PATH,
CodegenConstants.APIS);
// Micronaut declarative http client should contain custom added annotations
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/PetApi.java", "MyAdditionalAnnotation1(1,${param1})");
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/PetApi.java", "MyAdditionalAnnotation2(2,${param2})");
}
}