From e0179aefabb45ecf4f4df336146c33c6f4cfe5d7 Mon Sep 17 00:00:00 2001 From: altro3 Date: Fri, 9 Aug 2024 18:27:53 +0700 Subject: [PATCH] Fix generating default values Fixed #1686 --- .../AbstractMicronautJavaCodegen.java | 415 +++++++++++++++--- .../AbstractMicronautKotlinCodegen.java | 218 ++++++++- .../java-micronaut/common/model/pojo.mustache | 8 +- .../client/params/cookieParams.mustache | 2 +- .../client/params/headerParams.mustache | 2 +- .../client/params/pathParams.mustache | 2 +- .../client/params/queryParams.mustache | 2 +- .../common/model/pojo.mustache | 4 +- .../JavaMicronautClientCodegenTest.java | 13 + .../KotlinMicronautClientCodegenTest.java | 13 + .../3_0/params-with-default-value.yml | 55 +++ 11 files changed, 635 insertions(+), 99 deletions(-) create mode 100644 openapi-generator/src/test/resources/3_0/params-with-default-value.yml diff --git a/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautJavaCodegen.java b/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautJavaCodegen.java index 7283427d4f..029842a6f7 100644 --- a/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautJavaCodegen.java +++ b/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautJavaCodegen.java @@ -15,16 +15,21 @@ */ 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.media.Content; +import io.swagger.v3.oas.models.media.MediaType; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.servers.Server; +import io.swagger.v3.parser.util.SchemaTypeUtil; 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; @@ -43,10 +48,17 @@ 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.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; @@ -56,6 +68,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import static io.micronaut.openapi.generator.Utils.DEFAULT_BODY_PARAM_NAME; @@ -69,6 +82,7 @@ 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.utils.OnceLogger.once; import static org.openapitools.codegen.utils.StringUtils.camelize; /** @@ -134,6 +148,8 @@ public abstract class AbstractMicronautJavaCodegen responseBodyMappings = new ArrayList<>(); protected Map allModels = new HashMap<>(); + private final Logger log = LoggerFactory.getLogger(getClass()); + protected AbstractMicronautJavaCodegen() { // CHECKSTYLE:OFF @@ -144,12 +160,12 @@ protected AbstractMicronautJavaCodegen() { visitable = false; testTool = OPT_TEST_JUNIT; outputFolder = this instanceof JavaMicronautClientCodegen ? - "generated-code/java-micronaut-client" : "generated-code/java-micronaut"; + "generated-code/java-micronaut-client" : "generated-code/java-micronaut"; apiPackage = "org.openapitools.api"; modelPackage = "org.openapitools.model"; invokerPackage = "org.openapitools"; artifactId = this instanceof JavaMicronautClientCodegen ? - "openapi-micronaut-client" : "openapi-micronaut"; + "openapi-micronaut-client" : "openapi-micronaut"; embeddedTemplateDir = templateDir = "templates/java-micronaut"; apiDocPath = "docs/apis"; modelDocPath = "docs/models"; @@ -165,19 +181,19 @@ protected AbstractMicronautJavaCodegen() { // Set implemented features for user information modifyFeatureSet(features -> features - .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 - )) + .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 + )) ); // Set additional properties @@ -204,7 +220,7 @@ protected AbstractMicronautJavaCodegen() { 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(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)); + "If true, each operation will be generated only once in the first assigned tag.", generateOperationOnlyForFirstTag)); CliOption testToolOption = new CliOption(OPT_TEST, "Specify which test tool to generate files for").defaultValue(testTool); Map testToolOptionMap = new HashMap<>(); testToolOptionMap.put(OPT_TEST_JUNIT, "Use JUnit as test tool"); @@ -226,14 +242,14 @@ protected AbstractMicronautJavaCodegen() { // Modify the DATE_LIBRARY option to only have supported values cliOptions.stream() - .filter(o -> o.getOpt().equals(DATE_LIBRARY)) - .findFirst() - .ifPresent(opt -> { - Map 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); - }); + .filter(o -> o.getOpt().equals(DATE_LIBRARY)) + .findFirst() + .ifPresent(opt -> { + Map 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()); @@ -247,8 +263,8 @@ protected AbstractMicronautJavaCodegen() { var micronautReservedWords = List.of( // special words "Object", "List", "File", "OffsetDateTime", "LocalDate", "LocalTime", - "Client", "Format", "QueryValue", "QueryParam", "PathVariable", "Header", "Cookie", - "Authorization", "Body", "application" + "Client", "Format", "QueryValue", "QueryParam", "PathVariable", "Header", "Cookie", + "Authorization", "Body", "application" ); reservedWords.addAll(micronautReservedWords); List.of( @@ -571,14 +587,10 @@ 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_1 -> - generateSwaggerAnnotations = OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_1; - 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"); + case OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_1 -> generateSwaggerAnnotations = OPT_GENERATE_SWAGGER_ANNOTATIONS_SWAGGER_1; + 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"); } } } @@ -586,10 +598,8 @@ private void maybeSetSwagger() { private void maybeSetTestTool() { if (additionalProperties.containsKey(OPT_TEST)) { switch ((String) additionalProperties.get(OPT_TEST)) { - case OPT_TEST_JUNIT, OPT_TEST_SPOCK -> - testTool = (String) additionalProperties.get(OPT_TEST); - default -> - throw new RuntimeException("Test tool \"" + additionalProperties.get(OPT_TEST) + "\" is not supported or misspelled."); + case OPT_TEST_JUNIT, OPT_TEST_SPOCK -> testTool = (String) additionalProperties.get(OPT_TEST); + default -> throw new RuntimeException("Test tool \"" + additionalProperties.get(OPT_TEST) + "\" is not supported or misspelled."); } } } @@ -643,6 +653,40 @@ public CodegenParameter fromParameter(Parameter p, Set imports) { } parameter.vendorExtensions.put("realName", realName); + Schema parameterSchema; + if (p.getSchema() != null) { + parameterSchema = unaliasSchema(p.getSchema()); + } else if (p.getContent() != null) { + Content content = p.getContent(); + if (content.size() > 1) { + once(log).warn("Multiple schemas found in content, returning only the first one"); + } + Map.Entry entry = content.entrySet().iterator().next(); + parameterSchema = entry.getValue().getSchema(); + } else { + parameterSchema = null; + } + if (parameterSchema != null && parameterSchema.get$ref() != null) { + parameterSchema = openAPI.getComponents().getSchemas().get(parameterSchema.get$ref().substring("#/components/schemas/".length())); + } + + String defaultValueInit; + var items = parameter.items; + if (items == null) { + defaultValueInit = calcDefaultValues(null, null, false, false, + false, false, false, false, false, parameterSchema).getLeft(); + } else { + defaultValueInit = calcDefaultValues(items.datatypeWithEnum, items.dataType, items.getIsEnumOrRef(), + parameter.isArray, items.isString, items.isNumeric, items.isFloat, items.isMap, items.isNullable, + parameterSchema).getLeft(); + } + if (ModelUtils.isEnumSchema(parameterSchema)) { + defaultValueInit = parameter.dataType + ".fromValue(" + defaultValueInit + ")"; + } + if (defaultValueInit != null) { + parameter.vendorExtensions.put("defaultValueInit", defaultValueInit); + } + addStrValueToEnum(parameter.items); return parameter; @@ -668,9 +712,250 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo } property.vendorExtensions.put("realName", realName); + if (p != null && p.get$ref() != null) { + p = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, p.get$ref()); + } + + String defaultValueInit; + var items = property.items; + if (items == null) { + defaultValueInit = calcDefaultValues(null, null, false, false, + false, false, false, false, false, p).getLeft(); + } else { + defaultValueInit = calcDefaultValues(items.datatypeWithEnum, items.dataType, items.getIsEnumOrRef(), + property.isArray, items.isString, items.isNumeric, items.isFloat, items.isMap, items.isNullable, + p).getLeft(); + } + if (p != null && ModelUtils.isEnumSchema(p)) { + defaultValueInit = property.dataType + "." + toEnumVarName(property.defaultValue, property.dataType); + } + if (defaultValueInit != null) { + property.vendorExtensions.put("defaultValueInit", defaultValueInit); + } + return property; } + @Override + public String toEnumVarName(String value, String datatype) { + if (value == null) { + return null; + } + return super.toEnumVarName(value, datatype); + } + + @Override + public String toDefaultValue(CodegenProperty cp, Schema schema) { + + if (cp.items != null) { + return calcDefaultValues(cp.items.datatypeWithEnum, cp.items.dataType, cp.items.getIsEnumOrRef(), + cp.isArray, cp.items.isString, cp.items.isNumeric, cp.items.isFloat, cp.items.isMap, cp.items.isNullable, + schema).getRight(); + } else { + return calcDefaultValues(null, null, false, + false, false, false, false, false, false, schema).getRight(); + } + } + + private Pair calcDefaultValues(String itemsDatatypeWithEnum, String itemsDataType, boolean itemsIsEnumOrRef, + boolean isArray, boolean itemsIsString, boolean itemsIsNumeric, + boolean itemsIsFloat, boolean itemsIsMap, boolean isNullable, Schema schema) { + + String defaultValueInit = null; + String defaultValueStr = null; + + schema = ModelUtils.getReferencedSchema(this.openAPI, schema); + if (ModelUtils.isArraySchema(schema)) { + if (schema.getDefault() == null) { + // nullable or containerDefaultToNull set to true + if (isNullable || containerDefaultToNull) { + return Pair.of(null, null); + } + return getDefaultCollectionType(schema); + } + return arrayDefaultValue(itemsDatatypeWithEnum, itemsDataType, itemsIsEnumOrRef, + isArray, itemsIsString, itemsIsNumeric, itemsIsFloat, itemsIsMap, schema); + } else if (ModelUtils.isMapSchema(schema) && !(ModelUtils.isComposedSchema(schema))) { + if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { + // object is complex object with free-form additional properties + if (schema.getDefault() != null) { + defaultValueInit = super.toDefaultValue(schema); + defaultValueStr = super.toDefaultValue(schema); + } + } + + // nullable or containerDefaultToNull set to true + if (isNullable || containerDefaultToNull) { + return Pair.of(null, null); + } + + if (ModelUtils.getAdditionalProperties(schema) == null) { + return Pair.of(null, null); + } + + defaultValueInit = String.format(Locale.ROOT, "new %s<>()", instantiationTypes().getOrDefault("map", "HashMap")); + defaultValueStr = null; + } else if (ModelUtils.isIntegerSchema(schema)) { + if (schema.getDefault() != null) { + if (SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) { + defaultValueInit = schema.getDefault().toString() + "L"; + } else { + defaultValueInit = schema.getDefault().toString(); + } + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isNumberSchema(schema)) { + if (schema.getDefault() != null) { + if (SchemaTypeUtil.FLOAT_FORMAT.equals(schema.getFormat())) { + defaultValueInit = schema.getDefault().toString() + "F"; + } else if (SchemaTypeUtil.DOUBLE_FORMAT.equals(schema.getFormat())) { + defaultValueInit = schema.getDefault().toString() + "D"; + } else { + defaultValueInit = "new BigDecimal(\"" + schema.getDefault().toString() + "\")"; + } + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isBooleanSchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = schema.getDefault().toString(); + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isURISchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = "URI.create(\"" + escapeText(String.valueOf(schema.getDefault())) + "\")"; + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isStringSchema(schema)) { + if (schema.getDefault() != null) { + if (schema.getDefault() instanceof Date date) { + if ("java8".equals(getDateLibrary())) { + LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + defaultValueInit = String.format(Locale.ROOT, "LocalDate.parse(\"%s\")", localDate.toString()); + defaultValueStr = localDate.toString(); + } + } else if (schema.getDefault() instanceof java.time.OffsetDateTime offsetDateTime) { + if ("java8".equals(getDateLibrary())) { + defaultValueInit = String.format(Locale.ROOT, "OffsetDateTime.parse(\"%s\", %s)", + offsetDateTime.atZoneSameInstant(ZoneId.systemDefault()), + "java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(java.time.ZoneId.systemDefault())"); + defaultValueStr = offsetDateTime.toString(); + } + } else if (schema.getDefault() instanceof UUID) { + defaultValueInit = "UUID.fromString(\"" + schema.getDefault() + "\")"; + defaultValueStr = schema.getDefault().toString(); + } else { + String def = schema.getDefault().toString(); + if (schema.getEnum() == null) { + defaultValueInit = "\"" + escapeText(def) + "\""; + defaultValueStr = escapeText(def); + } else { + // convert to enum var name later in postProcessModels + defaultValueInit = "\"" + def + "\""; + defaultValueStr = def; + } + } + } + } else if (ModelUtils.isObjectSchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = super.toDefaultValue(schema); + defaultValueStr = super.toDefaultValue(schema); + } + } else if (ModelUtils.isComposedSchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = super.toDefaultValue(schema); + defaultValueStr = super.toDefaultValue(schema); + } + } + + return Pair.of(defaultValueInit, defaultValueStr); + } + + // left - initStr, right - defaultStr + public Pair arrayDefaultValue(String itemsDatatypeWithEnum, String itemsDataType, boolean itemsIsEnumOrRef, + boolean isArray, boolean itemsIsString, boolean itemsIsNumeric, + boolean itemsIsFloat, boolean itemsIsMap, Schema schema) { + if (schema.getDefault() != null) { // has default value + if (isArray) { + List values = new ArrayList<>(); + + if (schema.getDefault() instanceof ArrayNode arrayNodeDefault) { // array of default values + if (arrayNodeDefault.isEmpty()) { // e.g. default: [] + return getDefaultCollectionType(schema); + } + List finalValues = values; + arrayNodeDefault.elements().forEachRemaining((element) -> finalValues.add(element.asText())); + } else if (schema.getDefault() instanceof Collection defCollection) { + List finalValues = values; + defCollection.forEach((element) -> finalValues.add(String.valueOf(element))); + } else { // single value + values = Collections.singletonList(String.valueOf(schema.getDefault())); + } + + String defaultValue; + String defaultValueInit; + + if (itemsIsEnumOrRef) { // inline or ref enum + var defaultValues = new ArrayList(); + for (String value : values) { + defaultValues.add(itemsDatatypeWithEnum + "." + toEnumVarName(value, itemsDataType)); + } + defaultValue = StringUtils.join(defaultValues, ", "); + } else if (!values.isEmpty()) { + if (itemsIsString) { // array item is string + defaultValue = String.format(Locale.ROOT, "\"%s\"", StringUtils.join(values, "\", \"")); + defaultValueInit = defaultValue; + } else if (itemsIsNumeric) { + defaultValue = String.join(", ", values); + defaultValueInit = values.stream() + .map(v -> { + if ("BigInteger".equals(itemsDataType)) { + return "new BigInteger(\"" + v + "\")"; + } else if ("BigDecimal".equals(itemsDataType)) { + return "new BigDecimal(\"" + v + "\")"; + } else if (itemsIsFloat) { + return v + "F"; + } else { + return v; + } + }) + .collect(Collectors.joining(", ")); + } else { // array item is non-string, e.g. integer + defaultValue = StringUtils.join(values, ", "); + } + } else { + return getDefaultCollectionType(schema); + } + + return getDefaultCollectionType(schema, defaultValue); + } + if (itemsIsMap) { // map + // TODO + return Pair.of(null, null); + } else { + throw new RuntimeException("Error. Codegen Property must be array/set/map: " + schema); + } + } + return Pair.of(null, null); + } + + private Pair getDefaultCollectionType(Schema schema) { + return getDefaultCollectionType(schema, null); + } + + private Pair getDefaultCollectionType(Schema schema, String defaultValues) { + String arrayFormat = "new %s<>(Arrays.asList(%s))"; + if (defaultValues == null || defaultValues.isEmpty()) { + defaultValues = ""; + arrayFormat = "new %s<>()"; + } + + if (ModelUtils.isSet(schema)) { + return Pair.of(String.format(Locale.ROOT, arrayFormat, instantiationTypes().getOrDefault("set", "LinkedHashSet"), + defaultValues), defaultValues); + } + return Pair.of(String.format(Locale.ROOT, arrayFormat, instantiationTypes().getOrDefault("array", "ArrayList"), defaultValues), defaultValues); + } + @Override public void setUseBeanValidation(boolean useBeanValidation) { this.useBeanValidation = useBeanValidation; @@ -743,7 +1028,7 @@ public void preprocessOpenAPI(OpenAPI openApi) { var found = false; for (var opParam : op.getParameters()) { if (Objects.equals(opParam.getName(), param.getName()) - && Objects.equals(opParam.get$ref(), param.get$ref())) { + && Objects.equals(opParam.get$ref(), param.get$ref())) { found = true; break; } @@ -800,18 +1085,18 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List models = allModels.stream() - .map(ModelMap::getModel) - .collect(Collectors.toMap(v -> v.classname, v -> v)); + .map(ModelMap::getModel) + .collect(Collectors.toMap(v -> v.classname, v -> v)); OperationMap operations = objs.getOperations(); List operationList = operations.getOperation(); for (CodegenOperation op : operationList) { // 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") + || op.httpMethod.equals("POST") + || op.httpMethod.equals("PATCH") + || op.httpMethod.equals("OPTIONS") + || op.httpMethod.equals("DELETE") ); normalizeExtraAnnotations(EXT_ANNOTATIONS_OPERATION, false, op.vendorExtensions); @@ -827,14 +1112,14 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List) m.allowableValues.get("values"); } example = getExampleValue(m.defaultValue, null, m.classname, true, - allowableValues, null, null, m.requiredVars, false, false); + allowableValues, null, null, m.requiredVars, false, false); groovyExample = getExampleValue(m.defaultValue, null, m.classname, true, - allowableValues, null, null, m.requiredVars, true, false); + allowableValues, null, null, m.requiredVars, true, false); } else { example = getExampleValue(null, null, op.returnType, false, null, - op.returnBaseType, null, null, false, false); + op.returnBaseType, null, null, false, false); groovyExample = getExampleValue(null, null, op.returnType, false, null, - op.returnBaseType, null, null, true, false); + op.returnBaseType, null, null, true, false); } op.vendorExtensions.put("example", example); op.vendorExtensions.put("groovyExample", groovyExample); @@ -845,22 +1130,22 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List !CONTENT_TYPE_ANY.equals(contentType.get("mediaType"))) - .toList(); + .filter(contentType -> !CONTENT_TYPE_ANY.equals(contentType.get("mediaType"))) + .toList(); op.produces = op.produces == null ? null : op.produces.stream() - .filter(contentType -> !CONTENT_TYPE_ANY.equals(contentType.get("mediaType"))) - .toList(); + .filter(contentType -> !CONTENT_TYPE_ANY.equals(contentType.get("mediaType"))) + .toList(); // is only default "application/json" media type if (op.consumes == null - || op.consumes.isEmpty() - || op.consumes.size() == 1 && CONTENT_TYPE_APPLICATION_JSON.equals(op.consumes.get(0).get("mediaType"))) { + || op.consumes.isEmpty() + || op.consumes.size() == 1 && CONTENT_TYPE_APPLICATION_JSON.equals(op.consumes.get(0).get("mediaType"))) { op.vendorExtensions.put("onlyDefaultConsumeOrEmpty", true); } // is only default "application/json" media type if (op.produces == null - || op.produces.isEmpty() - || op.produces.size() == 1 && CONTENT_TYPE_APPLICATION_JSON.equals(op.produces.get(0).get("mediaType"))) { + || op.produces.isEmpty() + || op.produces.size() == 1 && CONTENT_TYPE_APPLICATION_JSON.equals(op.produces.get(0).get("mediaType"))) { op.vendorExtensions.put("onlyDefaultProduceOrEmpty", true); } @@ -1097,7 +1382,7 @@ private void wrapOperationReturnType(CodegenOperation op, String wrapperType, bo private void processOperationWithResponseWrappers(CodegenOperation op) { boolean hasNon200StatusCodes = op.responses.stream().anyMatch( - response -> !"200".equals(response.code) && response.code.startsWith("2") + response -> !"200".equals(response.code) && response.code.startsWith("2") ); boolean hasNonMappedHeaders = !op.responseHeaders.isEmpty(); boolean requiresHttpResponse = hasNon200StatusCodes || hasNonMappedHeaders; @@ -1152,8 +1437,8 @@ public String toVarName(String name) { var firstNameChar = varName.toCharArray()[0]; var underscorePrefix = getUnderscorePrefix(name); varName = underscorePrefix - + (firstNameChar == '_' && !underscorePrefix.isEmpty() ? "" : Character.toLowerCase(firstNameChar)) - + varName.substring(1); + + (firstNameChar == '_' && !underscorePrefix.isEmpty() ? "" : Character.toLowerCase(firstNameChar)) + + varName.substring(1); } return varName; @@ -1164,8 +1449,8 @@ public String getterAndSetterCapitalize(String name) { var newName = super.getterAndSetterCapitalize(name); if (name.startsWith("_")) { newName = getUnderscorePrefix(name) - + Character.toLowerCase(newName.toCharArray()[0]) - + newName.substring(1); + + Character.toLowerCase(newName.toCharArray()[0]) + + newName.substring(1); } return newName; } @@ -1364,8 +1649,8 @@ protected String getPropertyExampleValue(CodegenProperty p, boolean groovy) { } public String getExampleValue( - String defaultValue, String example, String dataType, Boolean isModel, List allowableValues, - String itemsType, String itemsExample, List requiredVars, boolean groovy, boolean isProperty + String defaultValue, String example, String dataType, Boolean isModel, List allowableValues, + String itemsType, String itemsExample, List requiredVars, boolean groovy, boolean isProperty ) { example = defaultValue != null ? defaultValue : example; String containerType = dataType == null ? null : dataType.split("<")[0]; @@ -1477,7 +1762,7 @@ public String escapeTextGroovy(String text) { @Override protected ImmutableMap.Builder addMustacheLambdas() { return super.addMustacheLambdas() - .put("replaceDotsWithUnderscore", new ReplaceDotsWithUnderscoreLambda()); + .put("replaceDotsWithUnderscore", new ReplaceDotsWithUnderscoreLambda()); } public void setSerializationLibrary(final String serializationLibrary) { diff --git a/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java b/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java index af093fd55b..659a04cf33 100644 --- a/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java +++ b/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java @@ -15,6 +15,7 @@ */ 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; @@ -22,11 +23,14 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; 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.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; @@ -81,6 +85,7 @@ 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; @@ -626,12 +631,9 @@ 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"); + 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"); } } } @@ -828,10 +830,10 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List) m.allowableValues.get("values"); } example = getExampleValue(m.defaultValue, null, m.classname, true, - allowableValues, null, null, m.requiredVars, false); + allowableValues, null, null, m.requiredVars, false); } else { example = getExampleValue(null, null, op.returnType, false, null, - op.returnBaseType, null, null, false); + op.returnBaseType, null, null, false); } op.vendorExtensions.put("example", example); } @@ -1026,6 +1028,37 @@ public CodegenParameter fromParameter(Parameter p, Set imports) { } parameter.vendorExtensions.put("realName", realName); + Schema parameterSchema; + if (p.getSchema() != null) { + parameterSchema = unaliasSchema(p.getSchema()); + } else if (p.getContent() != null) { + Content content = p.getContent(); + if (content.size() > 1) { + once(log).warn("Multiple schemas found in content, returning only the first one"); + } + Map.Entry entry = content.entrySet().iterator().next(); + parameterSchema = entry.getValue().getSchema(); + } else { + parameterSchema = null; + } + if (parameterSchema != null && parameterSchema.get$ref() != null) { + parameterSchema = openAPI.getComponents().getSchemas().get(parameterSchema.get$ref().substring("#/components/schemas/".length())); + } + + String defaultValueInit; + var items = parameter.items; + if (items == null) { + defaultValueInit = calcDefaultValues(null, null, false, parameterSchema).getLeft(); + } else { + defaultValueInit = calcDefaultValues(items.datatypeWithEnum, items.dataType, items.getIsEnumOrRef(), parameterSchema).getLeft(); + } + if (parameterSchema != null && ModelUtils.isEnumSchema(parameterSchema)) { + defaultValueInit = parameter.dataType + "." + toEnumVarName(parameter.defaultValue, parameter.dataType); + } + if (defaultValueInit != null) { + parameter.vendorExtensions.put("defaultValueInit", defaultValueInit); + } + addStrValueToEnum(parameter.items); return parameter; @@ -1044,6 +1077,24 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo } property.vendorExtensions.put("realName", realName); + if (p != null && p.get$ref() != null) { + p = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, p.get$ref()); + } + + String defaultValueInit; + var items = property.items; + if (items == null) { + defaultValueInit = calcDefaultValues(null, null, false, p).getLeft(); + } else { + defaultValueInit = calcDefaultValues(items.datatypeWithEnum, items.dataType, items.getIsEnumOrRef(), p).getLeft(); + } + if (p != null && ModelUtils.isEnumSchema(p)) { + defaultValueInit = property.dataType + "." + toEnumVarName(property.defaultValue, property.dataType); + } + if (defaultValueInit != null) { + property.vendorExtensions.put("defaultValueInit", defaultValueInit); + } + return property; } @@ -1091,6 +1142,9 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation @Override public String toEnumVarName(String value, String datatype) { + if (value == null) { + return null; + } String modified; if (value.isEmpty()) { modified = "EMPTY"; @@ -1114,7 +1168,7 @@ public static boolean isNumeric(String str) { * @param imports The operation imports. */ private void processParametersWithAdditionalMappings(List params, Set imports) { - Map additionalMappings = new LinkedHashMap<>(); + var additionalMappings = new LinkedHashMap(); Iterator iter = params.iterator(); while (iter.hasNext()) { CodegenParameter param = iter.next(); @@ -1137,22 +1191,23 @@ private void processParametersWithAdditionalMappings(List para } for (ParameterMapping mapping : additionalMappings.values()) { - if (mapping.mappedType() != null) { - CodegenParameter newParam = new CodegenParameter(); - newParam.paramName = mapping.mappedName(); - newParam.required = true; - newParam.isModel = mapping.isValidated(); - - String typeName = makeSureImported(mapping.mappedType(), imports); - newParam.dataType = typeName; - - // Set the paramName if required - if (newParam.paramName == null) { - newParam.paramName = toParamName(typeName); - } + if (mapping.mappedType() == null) { + continue; + } + var newParam = new CodegenParameter(); + newParam.paramName = mapping.mappedName(); + newParam.required = true; + newParam.isModel = mapping.isValidated(); - params.add(newParam); + String typeName = makeSureImported(mapping.mappedType(), imports); + newParam.dataType = typeName; + + // Set the paramName if required + if (newParam.paramName == null) { + newParam.paramName = toParamName(typeName); } + + params.add(newParam); } } @@ -1350,7 +1405,7 @@ public Map postProcessAllModels(Map objs) private void processOneOfModels(CodegenModel model, Collection models) { if (!model.vendorExtensions.containsKey("x-is-one-of-interface") - || !Boolean.parseBoolean(model.vendorExtensions.get("x-is-one-of-interface").toString())) { + || !Boolean.parseBoolean(model.vendorExtensions.get("x-is-one-of-interface").toString())) { return; } @@ -1646,6 +1701,121 @@ public String getExampleValue( return example; } + @Override + public String toDefaultValue(CodegenProperty cp, Schema schema) { + if (cp.items != null) { + return calcDefaultValues(cp.items.datatypeWithEnum, cp.items.dataType, cp.items.getIsEnumOrRef(), schema).getRight(); + } else { + return calcDefaultValues(null, null, false, schema).getRight(); + } + } + + private Pair calcDefaultValues(String itemsDatatypeWithEnum, String itemsDataType, boolean itemsIsEnumOrRef, Schema schema) { + String defaultValueInit = null; + String defaultValueStr = null; + schema = ModelUtils.getReferencedSchema(this.openAPI, schema); + if (ModelUtils.isBooleanSchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = schema.getDefault().toString(); + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isDateSchema(schema)) { + // TODO + defaultValueInit = null; + defaultValueStr = null; + } else if (ModelUtils.isDateTimeSchema(schema)) { + // TODO + defaultValueInit = null; + defaultValueStr = null; + } else if (ModelUtils.isNumberSchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = fixNumberValue(schema.getDefault().toString(), schema); + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isIntegerSchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = fixNumberValue(schema.getDefault().toString(), schema); + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isURISchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = importMapping.get("URI") + ".create(\"" + schema.getDefault() + "\")"; + defaultValueStr = schema.getDefault().toString(); + } + } else if (ModelUtils.isArraySchema(schema) && itemsDatatypeWithEnum != null) { + var pair = toArrayDefaultValue(itemsDatatypeWithEnum, itemsDataType, itemsIsEnumOrRef, schema); + defaultValueInit = pair.getLeft(); + defaultValueStr = pair.getRight(); + } else if (ModelUtils.isStringSchema(schema)) { + if (schema.getDefault() != null) { + String def = schema.getDefault().toString(); + if (schema.getEnum() == null) { + defaultValueInit = "\"" + escapeText(def) + "\""; + defaultValueStr = escapeText(def); + } else { + // convert to enum var name later in postProcessModels + defaultValueInit = "\"" + def + "\""; + defaultValueStr = def; + } + } + } else if (ModelUtils.isObjectSchema(schema)) { + if (schema.getDefault() != null) { + defaultValueInit = super.toDefaultValue(schema); + defaultValueStr = super.toDefaultValue(schema); + } + } + + return Pair.of(defaultValueInit, defaultValueStr); + } + + private String fixNumberValue(String number, Schema p) { + if (ModelUtils.isFloatSchema(p)) { + return number + "F"; + } else if (ModelUtils.isDoubleSchema(p)) { + if (number.contains(".")) { + return number; + } + return number + ".0"; + } else if (ModelUtils.isLongSchema(p)) { + return number + "L"; + } + return number; + } + + // left - initStr, right - defaultStr + private Pair toArrayDefaultValue(String itemsDatatypeWithEnum, String itemsDataType, boolean itemsIsEnumOrRef, Schema schema) { + if (schema.getDefault() != null) { + String arrInstantiationType = ModelUtils.isSet(schema) ? "set" : "arrayList"; + + if (!(schema.getDefault() instanceof ArrayNode def)) { + return Pair.of(null, null); + } + if (def.isEmpty()) { + return Pair.of(arrInstantiationType + "Of()", null); + } + + var defaultContent = new StringBuilder(); + Schema itemsSchema = ModelUtils.getSchemaItems(schema); + def.elements().forEachRemaining((element) -> { + String defaultValue = element.asText(); + if (defaultValue != null) { + if (itemsIsEnumOrRef) { + String className = itemsDatatypeWithEnum; + String enumVarName = toEnumVarName(defaultValue, itemsDataType); + defaultContent.append(className).append(".").append(enumVarName).append(","); + } else { + itemsSchema.setDefault(defaultValue); + defaultValue = calcDefaultValues(itemsDatatypeWithEnum, itemsDataType, itemsIsEnumOrRef, itemsSchema).getRight(); + defaultContent.append(defaultValue).append(","); + } + } + }); + defaultContent.deleteCharAt(defaultContent.length() - 1); // remove trailing comma + return Pair.of(arrInstantiationType + "Of(" + defaultContent + ")", defaultContent.toString()); + } + return Pair.of(null, null); + } + @Override protected ImmutableMap.Builder addMustacheLambdas() { return super.addMustacheLambdas() diff --git a/openapi-generator/src/main/resources/templates/java-micronaut/common/model/pojo.mustache b/openapi-generator/src/main/resources/templates/java-micronaut/common/model/pojo.mustache index a16fba65e5..5bb4c35547 100644 --- a/openapi-generator/src/main/resources/templates/java-micronaut/common/model/pojo.mustache +++ b/openapi-generator/src/main/resources/templates/java-micronaut/common/model/pojo.mustache @@ -121,10 +121,10 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} {{#isContainer}} - private {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}} {{name}}{{#required}}{{^requiredPropertiesInConstructor}}{{#vendorExtensions.defaultValueIsNotNull}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/vendorExtensions.defaultValueIsNotNull}}{{/requiredPropertiesInConstructor}}{{/required}}; + private {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}} {{name}}{{#required}}{{^requiredPropertiesInConstructor}}{{#vendorExtensions.defaultValueIsNotNull}}{{#vendorExtensions.defaultValueInit}} = {{{.}}}{{/vendorExtensions.defaultValueInit}}{{/vendorExtensions.defaultValueIsNotNull}}{{/requiredPropertiesInConstructor}}{{/required}}; {{/isContainer}} {{^isContainer}} - {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#vendorExtensions.defaultValueIsNotNull}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/vendorExtensions.defaultValueIsNotNull}}; + {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#vendorExtensions.defaultValueIsNotNull}}{{#vendorExtensions.defaultValueInit}} = {{{.}}}{{/vendorExtensions.defaultValueInit}}{{/vendorExtensions.defaultValueIsNotNull}}; {{/isContainer}} {{/vendorExtensions.x-is-jackson-optional-nullable}} {{/formatNoEmptyLines}} @@ -284,7 +284,7 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE {{^vendorExtensions.x-is-jackson-optional-nullable}} {{^required}} if ({{name}} == null) { - {{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; + {{name}} = {{#vendorExtensions.defaultValueInit}}{{{.}}}{{/vendorExtensions.defaultValueInit}}{{^vendorExtensions.defaultValueInit}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/vendorExtensions.defaultValueInit}}; } {{/required}} {{name}}.add({{name}}Item); @@ -313,7 +313,7 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE {{^vendorExtensions.x-is-jackson-optional-nullable}} {{^required}} if ({{name}} == null) { - {{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; + {{name}} = {{#vendorExtensions.defaultValueInit}} = {{{.}}}{{/vendorExtensions.defaultValueInit}}{{^vendorExtensions.defaultValueInit}}new HashMap<>(){{/vendorExtensions.defaultValueInit}}; } {{/required}} {{name}}.put(key, {{name}}Item); diff --git a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/cookieParams.mustache b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/cookieParams.mustache index 27deee3754..16a1b9ba41 100644 --- a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/cookieParams.mustache +++ b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/cookieParams.mustache @@ -1 +1 @@ -{{#isCookieParam}}@CookieValue({{#defaultValue}}value = {{/defaultValue}}"{{baseName}}"{{#defaultValue}}, defaultValue = "{{defaultValue}}"{{/defaultValue}}) {{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{/isCookieParam}} \ No newline at end of file +{{#isCookieParam}}@CookieValue({{#defaultValue}}value = {{/defaultValue}}"{{baseName}}"{{#defaultValue}}, defaultValue = "{{defaultValue}}"{{/defaultValue}}) {{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{#vendorExtensions.defaultValueInit}} = {{{vendorExtensions.defaultValueInit}}}{{/vendorExtensions.defaultValueInit}}{{/isCookieParam}} \ No newline at end of file diff --git a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/headerParams.mustache b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/headerParams.mustache index 9f7670492c..403d020e4c 100644 --- a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/headerParams.mustache +++ b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/headerParams.mustache @@ -1 +1 @@ -{{#isHeaderParam}}@Header({{#defaultValue}}name = {{/defaultValue}}"{{baseName}}"{{#defaultValue}}, defaultValue = "{{{defaultValue}}}"{{/defaultValue}}) {{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{/isHeaderParam}} \ No newline at end of file +{{#isHeaderParam}}@Header({{#defaultValue}}name = {{/defaultValue}}"{{baseName}}"{{#defaultValue}}, defaultValue = "{{{defaultValue}}}"{{/defaultValue}}) {{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{#vendorExtensions.defaultValueInit}} = {{{vendorExtensions.defaultValueInit}}}{{/vendorExtensions.defaultValueInit}}{{/isHeaderParam}} \ No newline at end of file diff --git a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/pathParams.mustache b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/pathParams.mustache index 738f7de45a..a963b3ea42 100644 --- a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/pathParams.mustache +++ b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/pathParams.mustache @@ -1 +1 @@ -{{#isPathParam}}@PathVariable({{#defaultValue}}name = {{/defaultValue}}"{{baseName}}"{{#defaultValue}}, defaultValue = "{{{defaultValue}}}"{{/defaultValue}}) {{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{/isPathParam}} \ No newline at end of file +{{#isPathParam}}@PathVariable({{#defaultValue}}name = {{/defaultValue}}"{{baseName}}"{{#defaultValue}}, defaultValue = "{{{defaultValue}}}"{{/defaultValue}}) {{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{#vendorExtensions.defaultValueInit}} = {{{vendorExtensions.defaultValueInit}}}{{/vendorExtensions.defaultValueInit}}{{/isPathParam}} \ No newline at end of file diff --git a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/queryParams.mustache b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/queryParams.mustache index ebab7f84bb..071b8fbedd 100644 --- a/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/queryParams.mustache +++ b/openapi-generator/src/main/resources/templates/kotlin-micronaut/client/params/queryParams.mustache @@ -1 +1 @@ -{{#isQueryParam}}@QueryValue({{#defaultValue}}value = {{/defaultValue}}"{{{baseName}}}"{{!default value}}{{#defaultValue}}, defaultValue = "{{{defaultValue}}}"{{/defaultValue}}) {{!validation and type}}{{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{/isQueryParam}} \ No newline at end of file +{{#isQueryParam}}@QueryValue({{#defaultValue}}value = {{/defaultValue}}"{{{baseName}}}"{{!default value}}{{#defaultValue}}, defaultValue = "{{{defaultValue}}}"{{/defaultValue}}) {{!validation and type}}{{>common/params/validation}}{{>client/params/type}}{{{paramName}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{#vendorExtensions.defaultValueInit}} = {{{vendorExtensions.defaultValueInit}}}{{/vendorExtensions.defaultValueInit}}{{/isQueryParam}} \ No newline at end of file diff --git a/openapi-generator/src/main/resources/templates/kotlin-micronaut/common/model/pojo.mustache b/openapi-generator/src/main/resources/templates/kotlin-micronaut/common/model/pojo.mustache index e67c9d272f..e0e13f4872 100644 --- a/openapi-generator/src/main/resources/templates/kotlin-micronaut/common/model/pojo.mustache +++ b/openapi-generator/src/main/resources/templates/kotlin-micronaut/common/model/pojo.mustache @@ -48,12 +48,12 @@ {{#nonPublicApi}}internal {{/nonPublicApi}}{{#hasChildren}}open{{/hasChildren}}{{^hasChildren}}data{{/hasChildren}} class {{classname}}({{#formatNoEmptyLines}} {{#vendorExtensions.requiredVarsWithoutDiscriminator}} {{>common/model/field_annotations}} - {{#vendorExtensions.overridden}}override {{/vendorExtensions.overridden}}{{^vendorExtensions.overridden}}{{#hasChildren}}open {{/hasChildren}}{{/vendorExtensions.overridden}}var {{{name}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{^defaultValue}}{{#isNullable}} = null{{/isNullable}}{{/defaultValue}}, + {{#vendorExtensions.overridden}}override {{/vendorExtensions.overridden}}{{^vendorExtensions.overridden}}{{#hasChildren}}open {{/hasChildren}}{{/vendorExtensions.overridden}}var {{{name}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{#vendorExtensions.defaultValueInit}} = {{{.}}}{{/vendorExtensions.defaultValueInit}}{{^vendorExtensions.defaultValueInit}}{{#isNullable}} = null{{/isNullable}}{{/vendorExtensions.defaultValueInit}}, {{/vendorExtensions.requiredVarsWithoutDiscriminator}} {{#vendorExtensions.withInheritance}} {{#vendorExtensions.optionalVars}} {{>common/model/field_annotations}} - {{#vendorExtensions.hasChildren}}open {{/vendorExtensions.hasChildren}}var {{{name}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}} = {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}, + {{#vendorExtensions.hasChildren}}open {{/vendorExtensions.hasChildren}}var {{{name}}}: {{{vendorExtensions.typeWithEnumWithGenericAnnotations}}} = {{#vendorExtensions.defaultValueInit}}{{{.}}}{{/vendorExtensions.defaultValueInit}}{{^vendorExtensions.defaultValueInit}}null{{/vendorExtensions.defaultValueInit}}, {{/vendorExtensions.optionalVars}} {{/vendorExtensions.withInheritance}} {{/formatNoEmptyLines}}){{#parent}}: {{{parent}}}({{#vendorExtensions.requiredParentVarsWithoutDiscriminator}}{{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.requiredParentVarsWithoutDiscriminator}}) {{/parent}}{{#vendorExtensions.x-implements}}{{#parent}}, {{/parent}}{{^parent}}: {{/parent}}{{^-first}}, {{/-first}}{{{.}}}{{/vendorExtensions.x-implements}} {{openbrace}} diff --git a/openapi-generator/src/test/java/io/micronaut/openapi/generator/JavaMicronautClientCodegenTest.java b/openapi-generator/src/test/java/io/micronaut/openapi/generator/JavaMicronautClientCodegenTest.java index 24c5138a52..8cfe1a5a9c 100644 --- a/openapi-generator/src/test/java/io/micronaut/openapi/generator/JavaMicronautClientCodegenTest.java +++ b/openapi-generator/src/test/java/io/micronaut/openapi/generator/JavaMicronautClientCodegenTest.java @@ -504,4 +504,17 @@ void testOneOfWithoutDiscriminator() { "@JsonTypeInfo" ); } + + @Test + void testParamsWithDefaultValue() { + + var codegen = new JavaMicronautClientCodegen(); + String outputPath = generateFiles(codegen, "src/test/resources/3_0/params-with-default-value.yml", CodegenConstants.APIS, CodegenConstants.MODELS); + String path = outputPath + "src/main/java/org/openapitools/"; + + assertFileContains(path + "api/DefaultApi.java", + "@PathVariable(name = \"apiVersion\", defaultValue = \"v5\") @Nullable BrowseSearchOrdersApiVersionParameter apiVersio", + "@Header(name = \"Content-Type\", defaultValue = \"application/json\") @Nullable String contentType" + ); + } } diff --git a/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautClientCodegenTest.java b/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautClientCodegenTest.java index e18671819e..00e2553b35 100644 --- a/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautClientCodegenTest.java +++ b/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautClientCodegenTest.java @@ -550,4 +550,17 @@ void testOneOfWithoutDiscriminator() { "@JsonTypeInfo" ); } + + @Test + void testParamsWithDefaultValue() { + + var codegen = new KotlinMicronautClientCodegen(); + String outputPath = generateFiles(codegen, "src/test/resources/3_0/params-with-default-value.yml", CodegenConstants.APIS, CodegenConstants.MODELS); + String path = outputPath + "src/main/kotlin/org/openapitools/"; + + assertFileContains(path + "api/DefaultApi.kt", + "@PathVariable(name = \"apiVersion\", defaultValue = \"v5\") @Nullable apiVersion: BrowseSearchOrdersApiVersionParameter? = BrowseSearchOrdersApiVersionParameter.V5,", + "@Header(name = \"Content-Type\", defaultValue = \"application/json\") @Nullable contentType: String? = \"application/json\"" + ); + } } diff --git a/openapi-generator/src/test/resources/3_0/params-with-default-value.yml b/openapi-generator/src/test/resources/3_0/params-with-default-value.yml new file mode 100644 index 0000000000..94e4622ce0 --- /dev/null +++ b/openapi-generator/src/test/resources/3_0/params-with-default-value.yml @@ -0,0 +1,55 @@ +openapi: 3.0.0 +info: + title: 'Order API' + version: v6 +paths: + '/{apiVersion}/orders': + get: + summary: 'Browser/search multiple order' + operationId: browseSearchOrders + parameters: + - $ref: '#/components/parameters/XFavorToken' + - $ref: '#/components/parameters/ApiVersion' + - $ref: '#/components/parameters/ContentType' + responses: + 200: + description: OK + content: + application/json: + schema: + type: string +components: + parameters: + ApiVersion: + name: apiVersion + in: path + description: 'Api Version as a path parameter' + required: false + schema: + type: string + default: v5 + enum: + - v1 + - v2 + - v3 + - v4 + - v5 + - v6 + - v7 + ContentType: + name: Content-Type + in: header + required: false + schema: + type: string + default: application/json + nullable: true + XFavorToken: + name: X-Favor-Token + in: header + description: 'Legacy token header for v5 and older APIs' + required: false + schema: + type: string + default: null + nullable: true \ No newline at end of file