From 3ed62e125e8dd1483ceab2548090f88ec2e6df67 Mon Sep 17 00:00:00 2001
From: Sorin Florea <sflorea@gradle.com>
Date: Thu, 11 Aug 2022 07:18:24 +0200
Subject: [PATCH] Explode query params for kotlin generator

---
 .../libraries/jvm-retrofit2/api.mustache      |  5 +-
 .../jvm-retrofit2/explodedQueryParam.mustache |  1 +
 .../jvm-retrofit2/paramJavadoc.mustache       |  5 ++
 .../jvm-retrofit2/queryParam.mustache         |  1 +
 .../jvm-retrofit2/queryParams.mustache        |  2 +-
 .../kotlin/KotlinClientCodegenModelTest.java  | 89 ++++++++++++++-----
 6 files changed, 75 insertions(+), 28 deletions(-)
 create mode 100644 modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/explodedQueryParam.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/paramJavadoc.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParam.mustache

diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/api.mustache
index afc245c8f69..324ed046aba 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/api.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/api.mustache
@@ -60,10 +60,7 @@ interface {{classname}} {
      * {{notes}}
      * Responses:{{#responses}}
      *  - {{code}}: {{{message}}}{{/responses}}
-     *
-    {{#allParams}}
-     * @param {{{paramName}}} {{description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{#required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/required}}
-    {{/allParams}}
+     *{{>paramJavadoc}}
      * @return {{^useCoroutines}}[Call]<{{/useCoroutines}}{{#isResponseFile}}[ResponseBody]{{/isResponseFile}}{{^isResponseFile}}{{#returnType}}[{{{.}}}]{{/returnType}}{{^returnType}}[Unit]{{/returnType}}{{/isResponseFile}}{{^useCoroutines}}>{{/useCoroutines}}
      */
     {{#isDeprecated}}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/explodedQueryParam.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/explodedQueryParam.mustache
new file mode 100644
index 00000000000..3aaa4e67fbf
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/explodedQueryParam.mustache
@@ -0,0 +1 @@
+@Query("{{baseName}}") {{{baseName}}}: {{#collectionFormat}}{{#isCollectionFormatMulti}}{{{dataType}}}{{/isCollectionFormatMulti}}{{^isCollectionFormatMulti}}{{{collectionFormat.toUpperCase}}}Params{{/isCollectionFormatMulti}}{{/collectionFormat}}{{^collectionFormat}}{{{dataType}}}{{/collectionFormat}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/paramJavadoc.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/paramJavadoc.mustache
new file mode 100644
index 00000000000..fe821c41b4d
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/paramJavadoc.mustache
@@ -0,0 +1,5 @@
+{{#allParams}}{{#isDeepObject}}
+     * @param {{{paramName}}} {{description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{#required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/required}}{{/isDeepObject}}{{^isDeepObject}}{{#isExplode}}{{#hasVars}}{{#vars}}
+     * @param {{{baseName}}} {{description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{#required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/required}}{{/vars}}{{/hasVars}}{{^hasVars}}
+     * @param {{{paramName}}} {{description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{#required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/required}}{{/hasVars}}{{/isExplode}}{{^isExplode}}
+     * @param {{{paramName}}} {{description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{#required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/required}}{{/isExplode}}{{/isDeepObject}}{{/allParams}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParam.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParam.mustache
new file mode 100644
index 00000000000..f208422536b
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParam.mustache
@@ -0,0 +1 @@
+@Query("{{baseName}}") {{{paramName}}}: {{#collectionFormat}}{{#isCollectionFormatMulti}}{{{dataType}}}{{/isCollectionFormatMulti}}{{^isCollectionFormatMulti}}{{{collectionFormat.toUpperCase}}}Params{{/isCollectionFormatMulti}}{{/collectionFormat}}{{^collectionFormat}}{{{dataType}}}{{/collectionFormat}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParams.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParams.mustache
index e5dfc022374..ab229cf40e4 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParams.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/queryParams.mustache
@@ -1 +1 @@
-{{#isQueryParam}}@Query("{{baseName}}") {{{paramName}}}: {{#collectionFormat}}{{#isCollectionFormatMulti}}{{{dataType}}}{{/isCollectionFormatMulti}}{{^isCollectionFormatMulti}}{{{collectionFormat.toUpperCase}}}Params{{/isCollectionFormatMulti}}{{/collectionFormat}}{{^collectionFormat}}{{{dataType}}}{{/collectionFormat}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}{{/isQueryParam}}
\ No newline at end of file
+{{#isQueryParam}}{{#isDeepObject}}{{>queryParam}}{{/isDeepObject}}{{^isDeepObject}}{{#isExplode}}{{#hasVars}}{{#vars}}{{>explodedQueryParam}}{{^-last}}, {{/-last}}{{/vars}}{{/hasVars}}{{^hasVars}}{{>queryParam}}{{/hasVars}}{{/isExplode}}{{^isExplode}}{{>queryParam}}{{/isExplode}}{{/isDeepObject}}{{/isQueryParam}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java
index af6005a2483..902b7fd9161 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java
@@ -18,48 +18,66 @@
 package org.openapitools.codegen.kotlin;
 
 import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.media.*;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.DateTimeSchema;
+import io.swagger.v3.oas.models.media.IntegerSchema;
+import io.swagger.v3.oas.models.media.MapSchema;
+import io.swagger.v3.oas.models.media.ObjectSchema;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.media.StringSchema;
+import org.openapitools.codegen.ClientOptInput;
+import org.openapitools.codegen.CodegenConstants;
 import org.openapitools.codegen.CodegenModel;
 import org.openapitools.codegen.CodegenProperty;
 import org.openapitools.codegen.DefaultCodegen;
+import org.openapitools.codegen.DefaultGenerator;
 import org.openapitools.codegen.TestUtils;
+import org.openapitools.codegen.config.CodegenConfigurator;
 import org.openapitools.codegen.languages.KotlinClientCodegen;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 @SuppressWarnings("static-method")
 public class KotlinClientCodegenModelTest {
 
     private Schema getArrayTestSchema() {
         return new ObjectSchema()
-                .description("a sample model")
-                .addProperties("id", new IntegerSchema().format("int64"))
-                .addProperties("examples", new ArraySchema().items(new StringSchema()))
-                .addRequiredItem("id");
+            .description("a sample model")
+            .addProperties("id", new IntegerSchema().format("int64"))
+            .addProperties("examples", new ArraySchema().items(new StringSchema()))
+            .addRequiredItem("id");
     }
 
     private Schema getSimpleSchema() {
         return new ObjectSchema()
-                .description("a sample model")
-                .addProperties("id", new IntegerSchema().format("int64"))
-                .addProperties("name", new StringSchema())
-                .addProperties("createdAt", new DateTimeSchema())
-                .addRequiredItem("id")
-                .addRequiredItem("name");
+            .description("a sample model")
+            .addProperties("id", new IntegerSchema().format("int64"))
+            .addProperties("name", new StringSchema())
+            .addProperties("createdAt", new DateTimeSchema())
+            .addRequiredItem("id")
+            .addRequiredItem("name");
     }
 
     private Schema getMapSchema() {
         return new ObjectSchema()
-                .description("a sample model")
-                .addProperties("mapping", new MapSchema()
-                        .additionalProperties(new StringSchema()));
+            .description("a sample model")
+            .addProperties("mapping", new MapSchema()
+                .additionalProperties(new StringSchema()));
     }
 
     private Schema getComplexSchema() {
         return new ObjectSchema()
-                .description("a sample model")
-                .addProperties("child", new ObjectSchema().$ref("#/components/schemas/Child"));
+            .description("a sample model")
+            .addProperties("child", new ObjectSchema().$ref("#/components/schemas/Child"));
     }
 
     @Test(description = "convert a simple model")
@@ -321,11 +339,11 @@ public class KotlinClientCodegenModelTest {
     @DataProvider(name = "modelNames")
     public static Object[][] modelNames() {
         return new Object[][]{
-                {"TestNs.TestClass", new ModelNameTest("TestNs.TestClass", "TestNsTestClass")},
-                {"$", new ModelNameTest("$", "Dollar")},
-                {"for", new ModelNameTest("`for`", "For")},
-                {"One<Two", new ModelNameTest("One<Two", "OneLessThanTwo")},
-                {"this is a test", new ModelNameTest("this is a test", "ThisIsATest")}
+            {"TestNs.TestClass", new ModelNameTest("TestNs.TestClass", "TestNsTestClass")},
+            {"$", new ModelNameTest("$", "Dollar")},
+            {"for", new ModelNameTest("`for`", "For")},
+            {"One<Two", new ModelNameTest("One<Two", "OneLessThanTwo")},
+            {"this is a test", new ModelNameTest("this is a test", "ThisIsATest")}
         };
     }
 
@@ -341,9 +359,34 @@ public class KotlinClientCodegenModelTest {
         Assert.assertEquals(cm.classname, testCase.expectedClassName);
     }
 
+    @Test
+    public void testNativeClientExplodedQueryParamObject() throws IOException {
+        Map<String, Object> properties = new HashMap<>();
+        properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
+
+        File output = Files.createTempDirectory("test").toFile();
+        output.deleteOnExit();
+
+        final CodegenConfigurator configurator = new CodegenConfigurator()
+            .setGeneratorName("kotlin")
+            .setLibrary("jvm-retrofit2")
+            .setAdditionalProperties(properties)
+            .setInputSpec("src/test/resources/3_0/issue4808.yaml")
+            .setOutputDir(output.getAbsolutePath().replace("\\", "/"));
+
+        final ClientOptInput clientOptInput = configurator.toClientOptInput();
+        DefaultGenerator generator = new DefaultGenerator();
+        List<File> files = generator.opts(clientOptInput).generate();
+
+        Assert.assertEquals(files.size(), 28);
+        TestUtils.assertFileContains(Paths.get(output + "/src/main/kotlin/xyz/abcdef/api/DefaultApi.kt"),
+            "fun getSomeValue(@Query(\"since\") since: kotlin.String? = null, @Query(\"sinceBuild\") sinceBuild: kotlin.String? = null, @Query(\"maxBuilds\") maxBuilds: kotlin.Int? = null, @Query(\"maxWaitSecs\") maxWaitSecs: kotlin.Int? = null)"
+        );
+    }
+
     private static class ModelNameTest {
-        private String expectedName;
-        private String expectedClassName;
+        private final String expectedName;
+        private final String expectedClassName;
 
         private ModelNameTest(String nameAndClass) {
             this.expectedName = nameAndClass;
-- 
GitLab