From 2758c5ea615932b3821194f6b20a400649b00dbe Mon Sep 17 00:00:00 2001 From: altro3 Date: Tue, 10 Dec 2024 00:49:18 +0700 Subject: [PATCH] Added default values for kotlin controller methods with optional parameters Fixed #1903 --- .../AbstractMicronautJavaCodegen.java | 5 ++ .../AbstractMicronautKotlinCodegen.java | 19 ++++++ .../server/controller-implementation.mustache | 11 +-- .../server/controller-interface.mustache | 2 +- .../AbstractMicronautCodegenTest.java | 3 +- .../JavaMicronautClientCodegenTest.java | 2 +- .../KotlinMicronautClientCodegenTest.java | 23 ++++++- .../KotlinMicronautServerCodegenTest.java | 67 ++++++++----------- .../3_0/optional-controller-values.yml | 38 +++++++++++ 9 files changed, 121 insertions(+), 49 deletions(-) create mode 100644 openapi-generator/src/test/resources/3_0/optional-controller-values.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 c1af4f2173..127fd2615c 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 @@ -735,6 +735,11 @@ public String toModelTestFilename(String name) { @Override public CodegenParameter fromParameter(Parameter p, Set imports) { var parameter = super.fromParameter(p, imports); + + if (parameter.isPathParam && !parameter.required) { + parameter.required = true; + } + checkPrimitives(parameter, unaliasSchema(p.getSchema())); // if name is escaped var realName = parameter.paramName; 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 1fe4e3d76f..25ea98fc52 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 @@ -29,6 +29,7 @@ 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.parameters.RequestBody; import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.servers.Server; import org.apache.commons.lang3.StringUtils; @@ -1421,9 +1422,24 @@ public String toModelName(final String name) { return schemaKeyToModelNameCache.get(name); } + @Override + public CodegenParameter fromRequestBody(RequestBody body, Set imports, String bodyParameterName) { + var rqBody = super.fromRequestBody(body, imports, bodyParameterName); + if (!rqBody.required) { + rqBody.vendorExtensions.put("defaultValueInit", "null"); + } + + return rqBody; + } + @Override public CodegenParameter fromParameter(Parameter p, Set imports) { var parameter = super.fromParameter(p, imports); + + if (parameter.isPathParam && !parameter.required) { + parameter.required = true; + } + checkPrimitives(parameter, unaliasSchema(p.getSchema())); // if name is escaped var realName = parameter.paramName; @@ -1462,6 +1478,9 @@ public CodegenParameter fromParameter(Parameter p, Set imports) { defaultValueInit = parameter.dataType + "." + enumVarName; } } + if (defaultValueInit == null && !parameter.required) { + defaultValueInit = "null"; + } if (defaultValueInit != null) { parameter.vendorExtensions.put("defaultValueInit", defaultValueInit); } diff --git a/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-implementation.mustache b/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-implementation.mustache index cec290663f..b8448b3f4c 100644 --- a/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-implementation.mustache +++ b/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-implementation.mustache @@ -18,12 +18,13 @@ import {{import}} {{/imports}} @Controller -class {{controllerClassname}}: {{classname}} { - +class {{controllerClassname}} : {{classname}} { {{#operations}} - {{#operation}} - {{!the method definition}} - override fun {{nickname}}({{#allParams}}{{{paramName}}}: {{{vendorExtensions.typeWithGenericAnnotations}}}{{^-last}}, {{/-last}}{{/allParams}}){{#returnType}}: {{{returnType}}}{{/returnType}} { + {{#operation}}{{#formatNoEmptyLines}} + override fun {{nickname}}({{#allParams}} + {{{paramName}}}: {{#isEnum}}{{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.typeWithGenericAnnotations}}}{{/isEnum}}{{#vendorExtensions.defaultValueInit}} = {{{.}}}{{/vendorExtensions.defaultValueInit}}, +{{/allParams}} + ){{#returnType}}: {{{returnType}}}{{/returnType}} {{openbrace}}{{/formatNoEmptyLines}} {{>server/controllerOperationBody}} } {{^-last}} diff --git a/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-interface.mustache b/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-interface.mustache index 1c0cbb6f7a..5fac6ab667 100644 --- a/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-interface.mustache +++ b/openapi-generator/src/main/resources/templates/kotlin-micronaut/server/controller-interface.mustache @@ -78,7 +78,7 @@ interface {{classname}} { {{#isDeprecated}} @java.lang.Deprecated {{/isDeprecated}} - {{{paramName}}}: {{#isEnum}}{{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.typeWithGenericAnnotations}}}{{/isEnum}},{{/formatSingleLine}} + {{{paramName}}}: {{#isEnum}}{{{vendorExtensions.typeWithEnumWithGenericAnnotations}}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.typeWithGenericAnnotations}}}{{/isEnum}}{{#vendorExtensions.defaultValueInit}} = {{{.}}}{{/vendorExtensions.defaultValueInit}},{{/formatSingleLine}} {{/allParams}}){{#returnType}}: {{{returnType}}}{{/returnType}} {{/formatNoEmptyLines}} diff --git a/openapi-generator/src/test/java/io/micronaut/openapi/generator/AbstractMicronautCodegenTest.java b/openapi-generator/src/test/java/io/micronaut/openapi/generator/AbstractMicronautCodegenTest.java index 7d764ed82a..a33d00a96a 100644 --- a/openapi-generator/src/test/java/io/micronaut/openapi/generator/AbstractMicronautCodegenTest.java +++ b/openapi-generator/src/test/java/io/micronaut/openapi/generator/AbstractMicronautCodegenTest.java @@ -46,8 +46,7 @@ protected String generateFiles(MicronautCodeGenerator codegen, String configP .withOutputDirectory(output) .withOutputs(Arrays.stream(filesToGenerate) .map(MicronautCodeGeneratorEntryPoint.OutputKind::of) - .toList() - .toArray(new MicronautCodeGeneratorEntryPoint.OutputKind[0]) + .toArray(MicronautCodeGeneratorEntryPoint.OutputKind[]::new) ) .build() .generate(); 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 a97e93219c..c3d765ccb5 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 @@ -557,7 +557,7 @@ void testParamsWithDefaultValue() { assertFileContains(path + "api/DefaultApi.java", "@QueryValue(\"ids\") @Nullable List<@NotNull Integer> ids", - "@PathVariable(name = \"apiVersion\", defaultValue = \"v5\") @Nullable BrowseSearchOrdersApiVersionParameter apiVersio", + "@PathVariable(name = \"apiVersion\", defaultValue = \"v5\") @NotNull BrowseSearchOrdersApiVersionParameter apiVersion", "@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 9369d19ff8..6a64433345 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 @@ -647,7 +647,7 @@ void testParamsWithDefaultValue() { assertFileContains(path + "api/DefaultApi.kt", "@QueryValue(\"ids\") @Nullable ids: List<@NotNull Int>? = null,", "@Header(\"X-Favor-Token\") @Nullable xFavorToken: String? = null,", - "@PathVariable(name = \"apiVersion\", defaultValue = \"v5\") @Nullable apiVersion: BrowseSearchOrdersApiVersionParameter? = BrowseSearchOrdersApiVersionParameter.V5,", + "@PathVariable(name = \"apiVersion\", defaultValue = \"v5\") @NotNull apiVersion: BrowseSearchOrdersApiVersionParameter = BrowseSearchOrdersApiVersionParameter.V5,", "@Header(name = \"Content-Type\", defaultValue = \"application/json\") @Nullable contentType: String? = \"application/json\"", "@QueryValue(\"algorithm\") @Nullable algorithm: BrowseSearchOrdersAlgorithmParameter? = null" ); @@ -1447,4 +1447,25 @@ override fun hashCode(): Int = Objects.hash(super.hashCode()) """); } + + @Test + void testOptionalQueryValues() { + + var codegen = new KotlinMicronautClientCodegen(); + codegen.setGenerateSwaggerAnnotations(false); + String outputPath = generateFiles(codegen, "src/test/resources/3_0/optional-controller-values.yml", CodegenConstants.APIS, CodegenConstants.MODELS); + String path = outputPath + "src/main/kotlin/org/openapitools/"; + + assertFileContains(path + "api/DefaultApi.kt", + """ + @Post("/sendPrimitives/{name}") + fun sendPrimitives( + @PathVariable("name") @NotNull name: String, + @QueryValue("brand") @Nullable brand: String? = null, + @CookieValue("coc") @Nullable coc: String? = null, + @Header("head") @Nullable head: String? = null, + @Body @Nullable body: String? = null, + ): Mono + """); + } } diff --git a/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautServerCodegenTest.java b/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautServerCodegenTest.java index 236e3b3dd3..077584a5ef 100644 --- a/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautServerCodegenTest.java +++ b/openapi-generator/src/test/java/io/micronaut/openapi/generator/KotlinMicronautServerCodegenTest.java @@ -544,7 +544,12 @@ void testReservedWords() { ); String path = outputPath + "src/main/kotlin/org/openapitools/"; - assertFileContains(path + "controller/ParametersController.kt", "override fun callInterface(name: Class, `data`: String): Mono"); + assertFileContains(path + "controller/ParametersController.kt", """ + override fun callInterface( + name: Class, + `data`: String, + ): Mono { + """); assertFileContains(path + "api/ParametersApi.kt", "fun callInterface(", "@QueryValue(\"name\") @NotNull @Valid name: Class,", "@QueryValue(\"data\") @NotNull `data`: String", @@ -565,7 +570,7 @@ void testCommonPathParametersWithRef() { assertFileContains(path + "api/WeatherForecastApisApi.kt", "@Get(\"/v1/forecast/{id}\")", "@PathVariable(\"id\") @NotNull id: String,", - "@QueryValue(\"hourly\") @Nullable hourly: List?,"); + "@QueryValue(\"hourly\") @Nullable hourly: List? = null,"); assertFileContains(path + "model/V1ForecastIdGetHourlyParameterInner.kt", "enum class V1ForecastIdGetHourlyParameterInner(", @@ -653,8 +658,8 @@ void testMultipartFormData() { fun profilePasswordPost( @Header("WCToken") @NotNull wcToken: String, @Header("WCTrustedToken") @NotNull wcTrustedToken: String, - @Part("name") @Nullable name: String?, - @Part("file") @Nullable file: CompletedFileUpload?, + @Part("name") @Nullable name: String? = null, + @Part("file") @Nullable file: CompletedFileUpload? = null, ): Mono """); } @@ -671,7 +676,7 @@ void testMultipleContentTypesEndpoints() { @Consumes("application/json", "application/xml") @Secured(SecurityRule.IS_ANONYMOUS) fun myOp( - @Body @Nullable @Valid coordinates: Coordinates?, + @Body @Nullable @Valid coordinates: Coordinates? = null, ): Mono> """, """ @@ -679,8 +684,8 @@ fun myOp( @Consumes("multipart/form-data") @Secured(SecurityRule.IS_ANONYMOUS) fun myOp_1( - @Nullable @Valid coordinates: Coordinates?, - @Nullable file: CompletedFileUpload?, + @Nullable @Valid coordinates: Coordinates? = null, + @Nullable file: CompletedFileUpload? = null, ): Mono> """, """ @@ -688,7 +693,7 @@ fun myOp_1( @Consumes("application/yaml", "text/json") @Secured(SecurityRule.IS_ANONYMOUS) fun myOp_2( - @Body @Nullable @Valid mySchema: MySchema?, + @Body @Nullable @Valid mySchema: MySchema? = null, ): Mono> """); } @@ -835,46 +840,30 @@ void testSwaggerAnnotations() { @Produces("application/json", "application/xml") @Secured("write:pets", "read:pets") fun findPetsByStatus( - @QueryValue("status") @Nullable status: List<@NotNull String>?, + @QueryValue("status") @Nullable status: List<@NotNull String>? = null, ): Mono> """); } @Test - void testKspMode() { + void testOptionalQueryValues() { var codegen = new KotlinMicronautServerCodegen(); - codegen.setKsp(true); codegen.setGenerateSwaggerAnnotations(false); - String outputPath = generateFiles(codegen, "src/test/resources/3_0/spec.yml", CodegenConstants.APIS, CodegenConstants.MODELS); + String outputPath = generateFiles(codegen, "src/test/resources/3_0/optional-controller-values.yml", CodegenConstants.APIS, CodegenConstants.MODELS); String path = outputPath + "src/main/kotlin/org/openapitools/"; -// assertFileContains(path + "api/PetApi.kt", -// """ -// @Operation( -// operationId = "findPetsByStatus", -// summary = "Finds Pets by status", -// description = "Multiple status values can be provided with comma separated strings", -// responses = [ -// ApiResponse(responseCode = "200", description = "successful operation", content = [ -// Content(mediaType = "application/json", array = ArraySchema(schema = Schema(implementation = Pet::class))), -// Content(mediaType = "application/xml", array = ArraySchema(schema = Schema(implementation = Pet::class))), -// ]), -// ApiResponse(responseCode = "400", description = "Invalid status value"), -// ], -// parameters = [ -// Parameter(name = "status", description = "Status values that need to be considered for filter", `in` = ParameterIn.QUERY), -// ], -// security = [ -// SecurityRequirement(name = "petstore_auth", scopes = ["write:pets", "read:pets"]), -// ], -// ) -// @Get("/pet/findByStatus") -// @Produces("application/json", "application/xml") -// @Secured("write:pets", "read:pets") -// fun findPetsByStatus( -// @QueryValue("status") @Nullable status: List<@NotNull String>?, -// ): Mono> -// """); + assertFileContains(path + "api/DefaultApi.kt", + """ + @Post("/sendPrimitives/{name}") + @Secured(SecurityRule.IS_ANONYMOUS) + fun sendPrimitives( + @PathVariable("name") @NotNull name: String, + @QueryValue("brand") @Nullable brand: String? = null, + @CookieValue("coc") @Nullable coc: String? = null, + @Header("head") @Nullable head: String? = null, + @Body @Nullable body: String? = null, + ): Mono + """); } } diff --git a/openapi-generator/src/test/resources/3_0/optional-controller-values.yml b/openapi-generator/src/test/resources/3_0/optional-controller-values.yml new file mode 100644 index 0000000000..c87216ceb0 --- /dev/null +++ b/openapi-generator/src/test/resources/3_0/optional-controller-values.yml @@ -0,0 +1,38 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Compute API + description: API for the Compute Service +paths: + /sendPrimitives/{name}: + post: + operationId: sendPrimitives + parameters: + - in: query + name: brand + schema: + type: string + - in: path + name: name + schema: + type: string + - in: cookie + name: coc + schema: + type: string + - in: header + name: head + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: string