io.micronaut.openapi.generator.AbstractMicronautKotlinCodegen Maven / Gradle / Ivy
/*
* Copyright 2017-2023 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.openapi.generator;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import io.micronaut.openapi.generator.Formatting.ReplaceDotsWithUnderscoreLambda;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.servers.Server;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.atteo.evo.inflector.English;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenDiscriminator;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenModelFactory;
import org.openapitools.codegen.CodegenModelType;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenResponse;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.IJsonSchemaValidationProperties;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.VendorExtension;
import org.openapitools.codegen.config.GlobalSettings;
import org.openapitools.codegen.languages.AbstractKotlinCodegen;
import org.openapitools.codegen.languages.features.BeanValidationFeatures;
import org.openapitools.codegen.meta.features.ClientModificationFeature;
import org.openapitools.codegen.meta.features.DocumentationFeature;
import org.openapitools.codegen.meta.features.GlobalFeature;
import org.openapitools.codegen.meta.features.SchemaSupportFeature;
import org.openapitools.codegen.meta.features.SecurityFeature;
import org.openapitools.codegen.meta.features.WireFormatFeature;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.model.OperationMap;
import org.openapitools.codegen.model.OperationsMap;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.FORMAT_INT16;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.FORMAT_INT8;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.FORMAT_SHORT;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_BYTE;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_CHAR;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_CHARACTER;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_DOUBLE;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_FLOAT;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_INT;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_LONG;
import static io.micronaut.openapi.generator.MnSchemaTypeUtil.TYPE_SHORT;
import static io.micronaut.openapi.generator.Utils.DEFAULT_BODY_PARAM_NAME;
import static io.micronaut.openapi.generator.Utils.DIVIDE_OPERATIONS_BY_CONTENT_TYPE;
import static io.micronaut.openapi.generator.Utils.EXT_ANNOTATIONS_CLASS;
import static io.micronaut.openapi.generator.Utils.EXT_ANNOTATIONS_FIELD;
import static io.micronaut.openapi.generator.Utils.EXT_ANNOTATIONS_OPERATION;
import static io.micronaut.openapi.generator.Utils.EXT_ANNOTATIONS_SETTER;
import static io.micronaut.openapi.generator.Utils.addStrValueToEnum;
import static io.micronaut.openapi.generator.Utils.isDateType;
import static io.micronaut.openapi.generator.Utils.normalizeExtraAnnotations;
import static io.micronaut.openapi.generator.Utils.processGenericAnnotations;
import static io.micronaut.openapi.generator.Utils.readListOfStringsProperty;
import static io.swagger.v3.parser.util.SchemaTypeUtil.BYTE_FORMAT;
import static io.swagger.v3.parser.util.SchemaTypeUtil.INTEGER_TYPE;
import static org.openapitools.codegen.CodegenConstants.API_PACKAGE;
import static org.openapitools.codegen.CodegenConstants.INVOKER_PACKAGE;
import static org.openapitools.codegen.CodegenConstants.MODEL_PACKAGE;
import static org.openapitools.codegen.CodegenConstants.PACKAGE_NAME;
import static org.openapitools.codegen.languages.KotlinClientCodegen.DATE_LIBRARY;
import static org.openapitools.codegen.utils.OnceLogger.once;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
/**
* Base generator for Micronaut.
*
* @param The generator options builder.
*/
@SuppressWarnings("checkstyle:DesignForExtension")
public abstract class AbstractMicronautKotlinCodegen extends AbstractKotlinCodegen implements BeanValidationFeatures, MicronautCodeGenerator {
public static final String OPT_TITLE = "title";
public static final String OPT_TEST = "test";
public static final String OPT_TEST_JUNIT = "junit";
public static final String OPT_REQUIRED_PROPERTIES_IN_CONSTRUCTOR = "requiredPropertiesInConstructor";
public static final String OPT_USE_AUTH = "useAuth";
public static final String OPT_USE_PLURAL = "plural";
public static final String OPT_FLUX_FOR_ARRAYS = "fluxForArrays";
public static final String OPT_GENERATED_ANNOTATION = "generatedAnnotation";
public static final String OPT_VISITABLE = "visitable";
public static final String OPT_DATE_LIBRARY_ZONED_DATETIME = "ZONED_DATETIME";
public static final String OPT_DATE_LIBRARY_OFFSET_DATETIME = "OFFSET_DATETIME";
public static final String OPT_DATE_LIBRARY_LOCAL_DATETIME = "LOCAL_DATETIME";
public static final String OPT_DATE_FORMAT = "dateFormat";
public static final String OPT_DATE_TIME_FORMAT = "dateTimeFormat";
public static final String OPT_REACTIVE = "reactive";
public static final String OPT_GENERATE_HTTP_RESPONSE_ALWAYS = "generateHttpResponseAlways";
public static final String OPT_GENERATE_HTTP_RESPONSE_WHERE_REQUIRED = "generateHttpResponseWhereRequired";
public static final String OPT_APPLICATION_NAME = "applicationName";
public static final String OPT_GENERATE_SWAGGER_ANNOTATIONS = "generateSwaggerAnnotations";
public static final String OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_2 = "swagger2";
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 static final String OPT_IMPLICIT_HEADERS = "implicitHeaders";
public static final String OPT_IMPLICIT_HEADERS_REGEX = "implicitHeadersRegex";
public static final String OPT_USE_ENUM_CASE_INSENSITIVE = "useEnumCaseInsensitive";
public static final String OPT_KSP = "ksp";
public static final String ADDITIONAL_ONE_OF_TYPE_ANNOTATIONS = "additionalOneOfTypeAnnotations";
public static final String ADDITIONAL_ENUM_TYPE_ANNOTATIONS = "additionalEnumTypeAnnotations";
public static final String CONTENT_TYPE_APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json";
public static final String CONTENT_TYPE_MULTIPART_FORM_DATA = "multipart/form-data";
public static final String CONTENT_TYPE_ANY = "*/*";
private static final String MONO_CLASS_NAME = "reactor.core.publisher.Mono";
private static final String FLUX_CLASS_NAME = "reactor.core.publisher.Flux";
protected SecureRandom random = new SecureRandom();
protected String dateLibrary;
protected String title;
protected boolean useBeanValidation;
protected boolean visitable;
protected boolean plural = true;
protected boolean fluxForArrays;
protected boolean generatedAnnotation = true;
protected String testTool;
protected boolean requiredPropertiesInConstructor = true;
protected boolean reactive;
protected boolean generateHttpResponseAlways;
protected boolean generateHttpResponseWhereRequired = true;
protected boolean useEnumCaseInsensitive;
protected boolean ksp;
protected boolean implicitHeaders;
protected String implicitHeadersRegex;
protected String appName;
protected String dateFormat;
protected String dateTimeFormat;
protected String generateSwaggerAnnotations;
protected boolean generateOperationOnlyForFirstTag;
protected String serializationLibrary = SerializationLibraryKind.MICRONAUT_SERDE_JACKSON.name();
protected List parameterMappings = new ArrayList<>();
protected List responseBodyMappings = new ArrayList<>();
protected Map allModels = new HashMap<>();
protected List additionalOneOfTypeAnnotations = new LinkedList<>();
protected List additionalEnumTypeAnnotations = new LinkedList<>();
private final Logger log = LoggerFactory.getLogger(getClass());
private final Map schemaKeyToModelNameCache = new HashMap<>();
protected AbstractMicronautKotlinCodegen() {
languageSpecificPrimitives = Set.of(
"Byte",
"ByteArray",
"Short",
"Int",
"Long",
"Float",
"Double",
"Boolean",
"Char",
"String",
"Array",
"List",
"MutableList",
"Map",
"MutableMap",
"Set",
"MutableSet",
"Any"
);
defaultIncludes = Set.of(
"Byte",
"ByteArray",
"Short",
"Int",
"Long",
"Float",
"Double",
"Boolean",
"Char",
"Array",
"List",
"MutableList",
"Set",
"MutableSet",
"Map",
"MutableMap",
"Any"
);
// CHECKSTYLE:OFF
// Set all the fields
useJakartaEe = true;
useBeanValidation = true;
visitable = false;
hideGenerationTimestamp = false;
testTool = OPT_TEST_JUNIT;
outputFolder = this instanceof KotlinMicronautClientCodegen ?
"generated-code/kotlin-micronaut-client" : "generated-code/kotlin-micronaut";
apiPackage = "org.openapitools.api";
modelPackage = "org.openapitools.model";
packageName = "org.openapitools";
artifactId = this instanceof KotlinMicronautClientCodegen ?
"openapi-micronaut-client" : "openapi-micronaut";
embeddedTemplateDir = templateDir = "templates/kotlin-micronaut";
apiDocPath = "docs/apis";
modelDocPath = "docs/models";
dateLibrary = OPT_DATE_LIBRARY_ZONED_DATETIME;
reactive = true;
appName = artifactId;
generateSwaggerAnnotations = this instanceof KotlinMicronautClientCodegen ? OPT_GENERATE_SWAGGER_ANNOTATIONS_FALSE : OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_2;
generateOperationOnlyForFirstTag = this instanceof KotlinMicronautServerCodegen;
enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.UPPERCASE;
inlineSchemaOption.put("RESOLVE_INLINE_ENUMS", "true");
useOneOfInterfaces = true;
// CHECKSTYLE:ON
GlobalSettings.setProperty(DIVIDE_OPERATIONS_BY_CONTENT_TYPE, "true");
// Set implemented features for user information
modifyFeatureSet(features -> features
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML))
.includeDocumentationFeatures(
DocumentationFeature.Readme
)
.securityFeatures(EnumSet.of(
SecurityFeature.ApiKey,
SecurityFeature.BearerToken,
SecurityFeature.BasicAuth,
SecurityFeature.OAuth2_Implicit,
SecurityFeature.OAuth2_AuthorizationCode,
SecurityFeature.OAuth2_ClientCredentials,
SecurityFeature.OAuth2_Password,
SecurityFeature.OpenIDConnect,
SecurityFeature.SignatureAuth,
SecurityFeature.AWSV4Signature
))
.excludeGlobalFeatures(GlobalFeature.XMLStructureDefinitions, GlobalFeature.Callbacks, GlobalFeature.LinkObjects, GlobalFeature.ParameterStyling)
.excludeSchemaSupportFeatures(SchemaSupportFeature.Polymorphism)
.includeClientModificationFeatures(ClientModificationFeature.BasePath)
);
// Set additional properties
additionalProperties.put("useOneOfInterfaces", useOneOfInterfaces);
additionalProperties.put("openbrace", "{");
additionalProperties.put("closebrace", "}");
// Set client options that will be presented to user
updateOption(INVOKER_PACKAGE, packageName);
updateOption(PACKAGE_NAME, packageName);
updateOption(CodegenConstants.ARTIFACT_ID, artifactId);
updateOption(CodegenConstants.API_PACKAGE, apiPackage);
updateOption(CodegenConstants.MODEL_PACKAGE, modelPackage);
cliOptions.add(new CliOption(OPT_TITLE, "Client service name").defaultValue(title));
cliOptions.add(new CliOption(OPT_APPLICATION_NAME, "Micronaut application name (Defaults to the " + CodegenConstants.ARTIFACT_ID + " value)").defaultValue(appName));
cliOptions.add(CliOption.newString(ADDITIONAL_ENUM_TYPE_ANNOTATIONS, "Additional annotations for enum type (class level annotations)"));
cliOptions.add(CliOption.newString(ADDITIONAL_ONE_OF_TYPE_ANNOTATIONS, "Additional annotations for oneOf interfaces (class level annotations). List separated by semicolon(;) or new line (Linux or Windows)"));
cliOptions.add(CliOption.newBoolean(OPT_USE_PLURAL, "Whether or not to use plural for request body parameter name", plural));
cliOptions.add(CliOption.newBoolean(OPT_FLUX_FOR_ARRAYS, "Whether or not to use Flux> instead Mono> for arrays in generated code", fluxForArrays));
cliOptions.add(CliOption.newBoolean(OPT_GENERATED_ANNOTATION, "Generate code with \"@Generated\" annotation", generatedAnnotation));
cliOptions.add(CliOption.newBoolean(OPT_KSP, "Generate code compatible only with KSP", ksp));
cliOptions.add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations", useBeanValidation));
cliOptions.add(CliOption.newBoolean(OPT_VISITABLE, "Generate visitor for subtypes with a discriminator", visitable));
cliOptions.add(CliOption.newBoolean(OPT_REQUIRED_PROPERTIES_IN_CONSTRUCTOR, "Allow only to create models with all the required properties provided in constructor", requiredPropertiesInConstructor));
cliOptions.add(CliOption.newBoolean(OPT_REACTIVE, "Make the responses use Reactor Mono as wrapper", reactive));
cliOptions.add(CliOption.newBoolean(OPT_IMPLICIT_HEADERS, "Skip header parameters in the generated API methods using @ApiImplicitParams annotation.", implicitHeaders));
cliOptions.add(CliOption.newString(OPT_IMPLICIT_HEADERS_REGEX, "Skip header parameters that matches given regex in the generated API methods using @ApiImplicitParams annotation. Note: this parameter is ignored when implicitHeaders=true"));
cliOptions.add(CliOption.newBoolean(OPT_GENERATE_HTTP_RESPONSE_ALWAYS, "Always wrap the operations response in HttpResponse object", generateHttpResponseAlways));
cliOptions.add(CliOption.newBoolean(OPT_GENERATE_HTTP_RESPONSE_WHERE_REQUIRED, "Wrap the operations response in HttpResponse object where non-200 HTTP status codes or additional headers are defined", generateHttpResponseWhereRequired));
cliOptions.add(CliOption.newBoolean(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC, isHideGenerationTimestamp()));
cliOptions.add(CliOption.newBoolean(OPT_GENERATE_OPERATION_ONLY_FOR_FIRST_TAG, "When false, the operation method will be duplicated in each of the tags if multiple tags are assigned to this operation. " +
"If true, each operation will be generated only once in the first assigned tag.", generateOperationOnlyForFirstTag));
cliOptions.add(CliOption.newBoolean(OPT_USE_ENUM_CASE_INSENSITIVE, "Use `equalsIgnoreCase` when String for enum comparison", useEnumCaseInsensitive));
var testToolOption = new CliOption(OPT_TEST, "Specify which test tool to generate files for").defaultValue(testTool);
var testToolOptionMap = new HashMap();
testToolOptionMap.put(OPT_TEST_JUNIT, "Use JUnit as test tool");
testToolOption.setEnum(testToolOptionMap);
cliOptions.add(testToolOption);
var generateSwaggerAnnotationsOption = new CliOption(OPT_GENERATE_SWAGGER_ANNOTATIONS, "Specify if you want to generate swagger annotations and which version").defaultValue(generateSwaggerAnnotations);
var generateSwaggerAnnotationsOptionMap = new HashMap();
generateSwaggerAnnotationsOptionMap.put(OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_2, "Use io.swagger.core.v3:swagger-annotations for annotating operations and schemas");
generateSwaggerAnnotationsOptionMap.put(OPT_GENERATE_SWAGGER_ANNOTATIONS_TRUE, "Equivalent to \"" + OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_2 + "\"");
generateSwaggerAnnotationsOptionMap.put(OPT_GENERATE_SWAGGER_ANNOTATIONS_FALSE, "Do not generate swagger annotations");
generateSwaggerAnnotationsOption.setEnum(generateSwaggerAnnotationsOptionMap);
cliOptions.add(generateSwaggerAnnotationsOption);
cliOptions.add(new CliOption(OPT_DATE_FORMAT, "Specify the format pattern of date as a string"));
cliOptions.add(new CliOption(OPT_DATE_TIME_FORMAT, "Specify the format pattern of date-time as a string"));
// Modify the DATE_LIBRARY option to only have supported values
cliOptions.stream()
.filter(o -> o.getOpt().equals(DATE_LIBRARY))
.findFirst()
.ifPresent(opt -> {
var valuesEnum = new HashMap();
valuesEnum.put(OPT_DATE_LIBRARY_OFFSET_DATETIME, opt.getEnum().get(OPT_DATE_LIBRARY_OFFSET_DATETIME));
valuesEnum.put(OPT_DATE_LIBRARY_LOCAL_DATETIME, opt.getEnum().get(OPT_DATE_LIBRARY_LOCAL_DATETIME));
opt.setEnum(valuesEnum);
});
final CliOption serializationLibraryOpt = CliOption.newString(CodegenConstants.SERIALIZATION_LIBRARY, "Serialization library for model");
serializationLibraryOpt.defaultValue(SerializationLibraryKind.JACKSON.name());
var serializationLibraryOptions = new HashMap();
serializationLibraryOptions.put(SerializationLibraryKind.JACKSON.name(), "Jackson as serialization library");
serializationLibraryOptions.put(SerializationLibraryKind.MICRONAUT_SERDE_JACKSON.name(), "Use micronaut-serialization with Jackson annotations");
serializationLibraryOpt.setEnum(serializationLibraryOptions);
cliOptions.removeIf(opt -> opt.getOpt().equals(CodegenConstants.SERIALIZATION_LIBRARY));
cliOptions.add(serializationLibraryOpt);
// Add reserved words
reservedWords.addAll(List.of(
"Client",
"Format",
"QueryValue",
"QueryParam",
"PathVariable",
"Header",
"Cookie",
"Authorization",
"Body",
"application"
));
// reservedWords.remove("value");
typeMapping.put("string", "String");
typeMapping.put("boolean", "Boolean");
typeMapping.put("integer", "Int");
typeMapping.put("float", "Float");
typeMapping.put("long", "Long");
typeMapping.put("double", "Double");
typeMapping.put("ByteArray", "ByteArray");
typeMapping.put("number", "BigDecimal");
typeMapping.put("decimal", "BigDecimal");
typeMapping.put("file", "File");
typeMapping.put("array", "List");
typeMapping.put("list", "List");
typeMapping.put("set", "Set");
typeMapping.put("map", "Map");
typeMapping.put("object", "Any");
typeMapping.put("binary", "ByteArray");
typeMapping.put("AnyType", "Any");
typeMapping.put("DateTime", "Instant");
typeMapping.put("date-time", "OffsetDateTime");
typeMapping.put("date", "LocalDate");
typeMapping.put("Date", "LocalDate");
typeMapping.put("LocalDateTime", "LocalDateTime");
typeMapping.put("OffsetDateTime", "OffsetDateTime");
typeMapping.put("ZonedDateTime", "ZonedDateTime");
typeMapping.put("LocalDate", "LocalDate");
typeMapping.put("LocalTime", "LocalTime");
instantiationTypes.put("array", "ArrayList");
instantiationTypes.put("list", "ArrayList");
instantiationTypes.put("map", "HashMap");
importMapping.put("File", "java.io.File");
importMapping.put("BigDecimal", "java.math.BigDecimal");
importMapping.put("DateTime", "java.time.Instant");
importMapping.put("LocalDateTime", "java.time.LocalDateTime");
importMapping.put("OffsetDateTime", "java.time.OffsetDateTime");
importMapping.put("ZonedDateTime", "java.time.ZonedDateTime");
importMapping.put("LocalDate", "java.time.LocalDate");
importMapping.put("LocalTime", "java.time.LocalTime");
}
public void setGenerateHttpResponseAlways(boolean generateHttpResponseAlways) {
this.generateHttpResponseAlways = generateHttpResponseAlways;
}
public void setGenerateHttpResponseWhereRequired(boolean generateHttpResponseWhereRequired) {
this.generateHttpResponseWhereRequired = generateHttpResponseWhereRequired;
}
public void setReactive(boolean reactive) {
this.reactive = reactive;
}
public void setImplicitHeaders(boolean implicitHeaders) {
this.implicitHeaders = implicitHeaders;
}
public void setImplicitHeadersRegex(String implicitHeadersRegex) {
this.implicitHeadersRegex = implicitHeadersRegex;
}
public void setTestTool(String testTool) {
this.testTool = testTool;
}
@Override
public void setArtifactId(String artifactId) {
super.setArtifactId(artifactId);
updateOption(CodegenConstants.ARTIFACT_ID, artifactId);
}
@Override
public void setModelPackage(String modelPackage) {
super.setModelPackage(modelPackage);
updateOption(CodegenConstants.MODEL_PACKAGE, modelPackage);
}
@Override
public void setApiPackage(String apiPackage) {
super.setApiPackage(apiPackage);
updateOption(CodegenConstants.API_PACKAGE, apiPackage);
}
@Override
public void setPackageName(String packageName) {
super.setPackageName(packageName);
updateOption(INVOKER_PACKAGE, packageName);
updateOption(PACKAGE_NAME, packageName);
}
public void setPlural(boolean plural) {
this.plural = plural;
}
public void setFluxForArrays(boolean fluxForArrays) {
this.fluxForArrays = fluxForArrays;
}
public void setGeneratedAnnotation(boolean generatedAnnotation) {
this.generatedAnnotation = generatedAnnotation;
}
public void setKsp(boolean ksp) {
this.ksp = ksp;
}
@Override
public void processOpts() {
// need it to add ability to set List in `additionalModelTypeAnnotations` property
if (additionalProperties.containsKey(ADDITIONAL_MODEL_TYPE_ANNOTATIONS)) {
setAdditionalModelTypeAnnotations(readListOfStringsProperty(ADDITIONAL_MODEL_TYPE_ANNOTATIONS, additionalProperties));
additionalProperties.remove(ADDITIONAL_MODEL_TYPE_ANNOTATIONS);
}
if (additionalProperties.containsKey(ADDITIONAL_ONE_OF_TYPE_ANNOTATIONS)) {
setAdditionalOneOfTypeAnnotations(readListOfStringsProperty(ADDITIONAL_ONE_OF_TYPE_ANNOTATIONS, additionalProperties));
additionalProperties.remove(ADDITIONAL_ONE_OF_TYPE_ANNOTATIONS);
}
if (additionalProperties.containsKey(ADDITIONAL_ENUM_TYPE_ANNOTATIONS)) {
setAdditionalEnumTypeAnnotations(readListOfStringsProperty(ADDITIONAL_ENUM_TYPE_ANNOTATIONS, additionalProperties));
additionalProperties.remove(ADDITIONAL_ENUM_TYPE_ANNOTATIONS);
}
super.processOpts();
// Get properties
if (additionalProperties.containsKey(OPT_TITLE)) {
title = (String) additionalProperties.get(OPT_TITLE);
}
if (additionalProperties.containsKey(INVOKER_PACKAGE)) {
packageName = (String) additionalProperties.get(INVOKER_PACKAGE);
} else {
additionalProperties.put(INVOKER_PACKAGE, packageName);
}
if (additionalProperties.containsKey(API_PACKAGE)) {
apiPackage = (String) additionalProperties.get(API_PACKAGE);
} else {
additionalProperties.put(API_PACKAGE, apiPackage);
}
if (additionalProperties.containsKey(MODEL_PACKAGE)) {
modelPackage = (String) additionalProperties.get(MODEL_PACKAGE);
} else {
additionalProperties.put(MODEL_PACKAGE, modelPackage);
}
if (additionalProperties.containsKey(OPT_APPLICATION_NAME)) {
appName = (String) additionalProperties.get(OPT_APPLICATION_NAME);
} else {
additionalProperties.put(OPT_APPLICATION_NAME, artifactId);
}
// Get boolean properties
if (additionalProperties.containsKey(USE_BEANVALIDATION)) {
useBeanValidation = convertPropertyToBoolean(USE_BEANVALIDATION);
}
writePropertyBack(USE_BEANVALIDATION, useBeanValidation);
if (additionalProperties.containsKey(OPT_USE_PLURAL)) {
plural = convertPropertyToBoolean(OPT_USE_PLURAL);
}
writePropertyBack(OPT_USE_PLURAL, plural);
if (additionalProperties.containsKey(OPT_FLUX_FOR_ARRAYS)) {
fluxForArrays = convertPropertyToBoolean(OPT_FLUX_FOR_ARRAYS);
}
writePropertyBack(OPT_FLUX_FOR_ARRAYS, fluxForArrays);
if (additionalProperties.containsKey(OPT_USE_ENUM_CASE_INSENSITIVE)) {
useEnumCaseInsensitive = convertPropertyToBoolean(OPT_USE_ENUM_CASE_INSENSITIVE);
}
writePropertyBack(OPT_USE_ENUM_CASE_INSENSITIVE, useEnumCaseInsensitive);
if (additionalProperties.containsKey(OPT_GENERATED_ANNOTATION)) {
generatedAnnotation = convertPropertyToBoolean(OPT_GENERATED_ANNOTATION);
}
writePropertyBack(OPT_GENERATED_ANNOTATION, generatedAnnotation);
if (additionalProperties.containsKey(OPT_KSP)) {
ksp = convertPropertyToBoolean(OPT_KSP);
}
writePropertyBack(OPT_KSP, ksp);
if (additionalProperties.containsKey(OPT_VISITABLE)) {
visitable = convertPropertyToBoolean(OPT_VISITABLE);
}
writePropertyBack(OPT_VISITABLE, visitable);
if (additionalProperties.containsKey(OPT_REQUIRED_PROPERTIES_IN_CONSTRUCTOR)) {
requiredPropertiesInConstructor = convertPropertyToBoolean(OPT_REQUIRED_PROPERTIES_IN_CONSTRUCTOR);
}
writePropertyBack(OPT_REQUIRED_PROPERTIES_IN_CONSTRUCTOR, requiredPropertiesInConstructor);
if (additionalProperties.containsKey(OPT_REACTIVE)) {
reactive = convertPropertyToBoolean(OPT_REACTIVE);
}
writePropertyBack(OPT_REACTIVE, reactive);
if (additionalProperties.containsKey(OPT_DATE_FORMAT)) {
dateFormat = (String) additionalProperties.get(OPT_DATE_FORMAT);
}
writePropertyBack(OPT_DATE_FORMAT, dateFormat);
if (additionalProperties.containsKey(OPT_DATE_TIME_FORMAT)) {
dateTimeFormat = (String) additionalProperties.get(OPT_DATE_TIME_FORMAT);
}
writePropertyBack(OPT_DATE_TIME_FORMAT, dateTimeFormat);
if (additionalProperties.containsKey(OPT_GENERATE_HTTP_RESPONSE_ALWAYS)) {
generateHttpResponseAlways = convertPropertyToBoolean(OPT_GENERATE_HTTP_RESPONSE_ALWAYS);
}
writePropertyBack(OPT_GENERATE_HTTP_RESPONSE_ALWAYS, generateHttpResponseAlways);
if (additionalProperties.containsKey(OPT_GENERATE_HTTP_RESPONSE_WHERE_REQUIRED)) {
generateHttpResponseWhereRequired = convertPropertyToBoolean(OPT_GENERATE_HTTP_RESPONSE_WHERE_REQUIRED);
}
writePropertyBack(OPT_GENERATE_HTTP_RESPONSE_WHERE_REQUIRED, generateHttpResponseWhereRequired);
if (additionalProperties.containsKey(OPT_GENERATE_OPERATION_ONLY_FOR_FIRST_TAG)) {
generateOperationOnlyForFirstTag = convertPropertyToBoolean(OPT_GENERATE_OPERATION_ONLY_FOR_FIRST_TAG);
}
writePropertyBack(OPT_GENERATE_OPERATION_ONLY_FOR_FIRST_TAG, generateOperationOnlyForFirstTag);
if (additionalProperties.containsKey(USE_JAKARTA_EE)) {
setUseJakartaEe(convertPropertyToBoolean(USE_JAKARTA_EE));
}
writePropertyBack(USE_JAKARTA_EE, useJakartaEe);
writePropertyBack(JAVAX_PACKAGE, useJakartaEe ? "jakarta" : "javax");
maybeSetTestTool();
writePropertyBack(OPT_TEST, testTool);
if (testTool.equals(OPT_TEST_JUNIT)) {
additionalProperties.put("isTestJunit", true);
}
convertPropertyToBooleanAndWriteBack(OPT_IMPLICIT_HEADERS, this::setImplicitHeaders);
convertPropertyToStringAndWriteBack(OPT_IMPLICIT_HEADERS_REGEX, this::setImplicitHeadersRegex);
maybeSetSwagger();
if (OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_2.equals(generateSwaggerAnnotations)) {
additionalProperties.put("generateSwagger2Annotations", true);
}
if (additionalProperties.containsKey(CodegenConstants.SERIALIZATION_LIBRARY)) {
setSerializationLibrary((String) additionalProperties.get(CodegenConstants.SERIALIZATION_LIBRARY));
}
additionalProperties.put(serializationLibrary.toLowerCase(Locale.US), true);
if (SerializationLibraryKind.MICRONAUT_SERDE_JACKSON.name().equals(serializationLibrary)) {
additionalProperties.put(SerializationLibraryKind.JACKSON.name().toLowerCase(Locale.US), true);
}
// Add all the supporting files
supportingFiles.add(new SupportingFile("common/configuration/application.yml.mustache", resourcesFolder, "application.yml").doNotOverwrite());
supportingFiles.add(new SupportingFile("common/configuration/logback.xml.mustache", resourcesFolder, "logback.xml").doNotOverwrite());
// Use the default java time
switch (dateLibrary) {
case OPT_DATE_LIBRARY_OFFSET_DATETIME -> {
typeMapping.put("DateTime", "OffsetDateTime");
typeMapping.put("date", "LocalDate");
}
case OPT_DATE_LIBRARY_ZONED_DATETIME -> {
typeMapping.put("DateTime", "ZonedDateTime");
typeMapping.put("date", "LocalDate");
}
case OPT_DATE_LIBRARY_LOCAL_DATETIME -> {
typeMapping.put("DateTime", "LocalDateTime");
typeMapping.put("date", "LocalDate");
}
default -> {
}
}
// Add documentation files
modelDocTemplateFiles.clear();
modelDocTemplateFiles.put("common/doc/model_doc.mustache", ".md");
// Add model files
modelTemplateFiles.clear();
modelTemplateFiles.put("common/model/model.mustache", ".kt");
// Add test files
modelTestTemplateFiles.clear();
if (testTool.equals(OPT_TEST_JUNIT)) {
modelTestTemplateFiles.put("common/test/model_test.mustache", ".kt");
}
// Set properties for documentation
final String invokerFolder = (sourceFolder + '/' + packageName).replace(".", "/");
final String apiFolder = (sourceFolder + '/' + apiPackage()).replace('.', '/');
final String modelFolder = (sourceFolder + '/' + modelPackage()).replace('.', '/');
additionalProperties.put("invokerFolder", invokerFolder);
additionalProperties.put("resourceFolder", resourcesFolder);
additionalProperties.put("apiFolder", apiFolder);
additionalProperties.put("modelFolder", modelFolder);
additionalProperties.put("formatNoEmptyLines", new Formatting.LineFormatter(0));
additionalProperties.put("formatOneEmptyLine", new Formatting.LineFormatter(1));
additionalProperties.put("formatSingleLine", new Formatting.SingleLineFormatter());
additionalProperties.put("indent", new Formatting.IndentFormatter(4));
}
public void addParameterMappings(List parameterMappings) {
this.parameterMappings.addAll(parameterMappings);
}
public void addResponseBodyMappings(List responseBodyMappings) {
this.responseBodyMappings.addAll(responseBodyMappings);
}
public void addSchemaMapping(Map schemaMapping) {
this.schemaMapping.putAll(schemaMapping);
}
public void addImportMapping(Map importMapping) {
this.importMapping.putAll(importMapping);
}
public void addNameMapping(Map nameMapping) {
this.nameMapping.putAll(nameMapping);
}
public void addTypeMapping(Map typeMapping) {
this.typeMapping.putAll(typeMapping);
}
public void addEnumNameMapping(Map enumNameMapping) {
this.enumNameMapping.putAll(enumNameMapping);
}
public void addModelNameMapping(Map modelNameMapping) {
this.modelNameMapping.putAll(modelNameMapping);
}
public void addInlineSchemaNameMapping(Map inlineSchemaNameMapping) {
this.inlineSchemaNameMapping.putAll(inlineSchemaNameMapping);
}
public void addInlineSchemaOption(Map inlineSchemaOption) {
this.inlineSchemaOption.putAll(inlineSchemaOption);
}
public void addOpenapiNormalizer(Map openapiNormalizer) {
this.openapiNormalizer.putAll(openapiNormalizer);
}
// CHECKSTYLE:OFF
private void maybeSetSwagger() {
if (additionalProperties.containsKey(OPT_GENERATE_SWAGGER_ANNOTATIONS)) {
String value = String.valueOf(additionalProperties.get(OPT_GENERATE_SWAGGER_ANNOTATIONS));
switch (value) {
case OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_2, OPT_GENERATE_SWAGGER_ANNOTATIONS_TRUE -> generateSwaggerAnnotations = OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_2;
case OPT_GENERATE_SWAGGER_ANNOTATIONS_FALSE -> generateSwaggerAnnotations = OPT_GENERATE_SWAGGER_ANNOTATIONS_FALSE;
default -> throw new RuntimeException("Value \"" + value + "\" for the " + OPT_GENERATE_SWAGGER_ANNOTATIONS + " parameter is unsupported or misspelled");
}
}
}
private void maybeSetTestTool() {
if (additionalProperties.containsKey(OPT_TEST)) {
if (additionalProperties.get(OPT_TEST).equals(OPT_TEST_JUNIT)) {
testTool = (String) additionalProperties.get(OPT_TEST);
} else {
throw new RuntimeException("Test tool \"" + additionalProperties.get(OPT_TEST) + "\" is not supported or misspelled.");
}
}
}
// CHECKSTYLE:ON
public String testFileFolder() {
return (getOutputDir() + "/src/test/kotlin/").replace('/', File.separatorChar);
}
public abstract boolean isServer();
@Override
public String apiTestFileFolder() {
return testFileFolder() + apiPackage().replace(".", "/");
}
@Override
public String modelTestFileFolder() {
return getOutputDir() + "/src/test/kotlin/" + modelPackage().replace('.', File.separatorChar);
}
@Override
public String toApiTestFilename(String name) {
return toApiName(name) + "Test";
}
@Override
public String toModelTestFilename(String name) {
return toModelName(name) + "Test";
}
@Override
public void setUseBeanValidation(boolean useBeanValidation) {
this.useBeanValidation = useBeanValidation;
}
public void setVisitable(boolean visitable) {
this.visitable = visitable;
}
@Override
protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema) {
var discriminator = super.createDiscriminator(schemaName, schema);
if (discriminator == null) {
return null;
}
for (var entry : discriminator.getMapping().entrySet()) {
String name;
if (entry.getValue().indexOf('/') < 0) {
continue;
}
name = ModelUtils.getSimpleRef(entry.getValue());
var referencedSchema = ModelUtils.getSchema(openAPI, name);
if (referencedSchema == null) {
once(log).error("Failed to lookup the schema '{}' when processing the discriminator mapping of oneOf/anyOf. Please check to ensure it's defined properly.", name);
continue;
}
if (referencedSchema.getProperties() == null || referencedSchema.getProperties().isEmpty()) {
continue;
}
boolean isDiscriminatorPropTypeFound = false;
var props = (Map) referencedSchema.getProperties();
for (var propEntry : props.entrySet()) {
if (!propEntry.getKey().equals(discriminator.getPropertyName())) {
continue;
}
discriminator.setPropertyType(getTypeDeclaration(propEntry.getValue()));
isDiscriminatorPropTypeFound = true;
break;
}
if (isDiscriminatorPropTypeFound) {
break;
}
}
return discriminator;
}
@Override
public String toApiVarName(String name) {
String apiVarName = super.toApiVarName(name);
if (reservedWords.contains(apiVarName)) {
apiVarName = escapeReservedWord(apiVarName);
}
return apiVarName;
}
public boolean isVisitable() {
return visitable;
}
@Override
public String sanitizeTag(String tag) {
// Skip sanitization to get the original tag name in the addOperationToGroup() method.
// Inside that method tag is manually sanitized.
return tag;
}
public String superSanitizeTag(String tag) {
tag = camelize(underscore(sanitizeName(tag)));
// tag starts with numbers
if (tag.matches("^\\d.*")) {
tag = "Class" + tag;
}
return tag;
}
@Override
public String toApiName(String name) {
return Utils.toApiName(name, apiNamePrefix, apiNameSuffix);
}
@Override
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co,
Map> operations) {
if (generateOperationOnlyForFirstTag && !co.tags.get(0).getName().equals(tag)) {
// This is not the first assigned to this operation tag;
return;
}
super.addOperationToGroup(superSanitizeTag(tag), resourcePath, operation, co, operations);
}
@Override
public void preprocessOpenAPI(OpenAPI openApi) {
if (openApi.getPaths() != null) {
for (var path : openApi.getPaths().values()) {
if (path.getParameters() == null || path.getParameters().isEmpty()) {
continue;
}
for (var op : path.readOperations()) {
if (op.getParameters() == null) {
op.setParameters(new ArrayList<>());
}
for (var param : path.getParameters()) {
var found = false;
for (var opParam : op.getParameters()) {
if (Objects.equals(opParam.getName(), param.getName())
&& Objects.equals(opParam.get$ref(), param.get$ref())) {
found = true;
break;
}
}
if (!found) {
op.getParameters().add(param);
}
}
}
}
}
var inlineModelResolver = new MicronautInlineModelResolver(openApi);
inlineModelResolver.setInlineSchemaNameMapping(inlineSchemaNameMapping);
inlineModelResolver.setInlineSchemaOptions(inlineSchemaOption);
inlineModelResolver.flatten();
var pathItems = openApi.getPaths();
// fix for ApiResponse with $ref
if (pathItems != null) {
for (var pathEntry : pathItems.entrySet()) {
for (var opEntry : pathEntry.getValue().readOperationsMap().entrySet()) {
// process all response bodies
if (opEntry.getValue().getResponses() != null) {
for (var responseEntry : opEntry.getValue().getResponses().entrySet()) {
var response = responseEntry.getValue();
var refResponse = ModelUtils.getReferencedApiResponse(openApi, response);
if (response.getContent() == null) {
response.setContent(refResponse.getContent());
}
if (response.getDescription() == null) {
response.setDescription(refResponse.getDescription());
}
if (response.getHeaders() == null || response.getHeaders().isEmpty()) {
response.setHeaders(refResponse.getHeaders());
}
if (response.getExtensions() == null || response.getExtensions().isEmpty()) {
response.setExtensions(refResponse.getExtensions());
}
if (response.getLinks() == null || response.getLinks().isEmpty()) {
response.setLinks(refResponse.getLinks());
}
}
}
}
}
}
super.preprocessOpenAPI(openApi);
if (openApi.getPaths() != null) {
for (Map.Entry openAPIGetPathsEntry : openApi.getPaths().entrySet()) {
PathItem path = openAPIGetPathsEntry.getValue();
var ops = path.readOperations();
if (ops == null || ops.isEmpty()) {
continue;
}
for (Operation operation : ops) {
log.info("Processing operation {}", operation.getOperationId());
if (hasBodyParameter(operation) || hasFormParameter(operation)) {
var defaultContentType = hasFormParameter(operation) ? CONTENT_TYPE_APPLICATION_FORM_URLENCODED : CONTENT_TYPE_APPLICATION_JSON;
var consumes = new ArrayList<>(getConsumesInfo(openApi, operation));
String contentType = consumes.isEmpty() ? defaultContentType : consumes.get(0);
operation.addExtension(VendorExtension.X_CONTENT_TYPE.getName(), contentType);
}
String[] accepts = getAccepts(openApi, operation);
operation.addExtension(VendorExtension.X_ACCEPTS.getName(), accepts);
}
}
}
}
private String[] getAccepts(OpenAPI openAPIArg, Operation operation) {
final Set producesInfo = getProducesInfo(openAPIArg, operation);
if (producesInfo != null && !producesInfo.isEmpty()) {
return producesInfo.toArray(new String[] {});
}
return new String[] { CONTENT_TYPE_APPLICATION_JSON }; // default media type
}
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) {
objs = super.postProcessOperationsWithModels(objs, allModels);
Map models = allModels.stream()
.map(ModelMap::getModel)
.collect(Collectors.toMap(v -> v.classname, v -> v));
OperationMap operations = objs.getOperations();
List operationList = operations.getOperation();
for (CodegenOperation op : operationList) {
handleImplicitHeaders(op);
handleConstantParams(op);
// Set whether body is supported in request
op.vendorExtensions.put("methodAllowsBody", op.httpMethod.equals("PUT")
|| op.httpMethod.equals("POST")
|| op.httpMethod.equals("PATCH")
|| op.httpMethod.equals("OPTIONS")
|| op.httpMethod.equals("DELETE")
);
if (StringUtils.isNotEmpty(op.notes)) {
op.notes = op.notes.strip();
}
if (StringUtils.isNotEmpty(op.summary)) {
op.summary = op.summary.strip();
}
normalizeExtraAnnotations(EXT_ANNOTATIONS_OPERATION, true, op.vendorExtensions);
// Set response example
if (op.returnType != null) {
String example;
if (models.containsKey(op.returnType)) {
CodegenModel m = models.get(op.returnType);
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy