diff --git a/bin/kotlin-client-petstore-multiplatform.sh b/bin/kotlin-client-petstore-multiplatform.sh
new file mode 100755
index 00000000000..a0b5de50b53
--- /dev/null
+++ b/bin/kotlin-client-petstore-multiplatform.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+SCRIPT="$0"
+echo "# START SCRIPT: $SCRIPT"
+
+while [ -h "$SCRIPT" ] ; do
+ ls=$(ls -ld "$SCRIPT")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' > /dev/null; then
+ SCRIPT="$link"
+ else
+ SCRIPT=$(dirname "$SCRIPT")/"$link"
+ fi
+done
+
+if [ ! -d "${APP_DIR}" ]; then
+ APP_DIR=$(dirname "$SCRIPT")/..
+ APP_DIR=$(cd "${APP_DIR}"; pwd)
+fi
+
+executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
+
+if [ ! -f "$executable" ]
+then
+ mvn -B clean package
+fi
+
+# if you've executed sbt assembly previously it will use that instead.
+export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
+ags="generate -t modules/openapi-generator/src/main/resources/kotlin-client -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g kotlin --artifact-id kotlin-client-petstore-multiplatform --library multiplatform -o samples/client/petstore/kotlin-multiplatform $@"
+
+java ${JAVA_OPTS} -jar ${executable} ${ags}
diff --git a/bin/openapi3/kotlin-client-petstore-multiplatform.sh b/bin/openapi3/kotlin-client-petstore-multiplatform.sh
new file mode 100755
index 00000000000..913f73ef1d8
--- /dev/null
+++ b/bin/openapi3/kotlin-client-petstore-multiplatform.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+SCRIPT="$0"
+echo "# START SCRIPT: $SCRIPT"
+
+while [ -h "$SCRIPT" ] ; do
+ ls=$(ls -ld "$SCRIPT")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' > /dev/null; then
+ SCRIPT="$link"
+ else
+ SCRIPT=$(dirname "$SCRIPT")/"$link"
+ fi
+done
+
+if [ ! -d "${APP_DIR}" ]; then
+ APP_DIR=$(dirname "$SCRIPT")/..
+ APP_DIR=$(cd "${APP_DIR}"; pwd)
+fi
+
+executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
+
+if [ ! -f "$executable" ]
+then
+ mvn clean package
+fi
+
+export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
+ags="generate -i modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml -t modules/openapi-generator/src/main/resources/kotlin-client -g kotlin --artifact-id kotlin-client-petstore-multiplatform --library multiplatform -o samples/openapi3/client/petstore/kotlin-multiplatform $@"
+
+echo "Cleaning previously generated files if any from samples/openapi3/client/petstore/kotlin-multiplatform"
+rm -rf samples/openapi3/client/petstore/kotlin-multiplatform
+
+echo "Generating Kotling client..."
+java $JAVA_OPTS -jar $executable $ags
diff --git a/bin/windows/kotlin-client-petstore-multiplatform.bat b/bin/windows/kotlin-client-petstore-multiplatform.bat
new file mode 100644
index 00000000000..628170d6007
--- /dev/null
+++ b/bin/windows/kotlin-client-petstore-multiplatform.bat
@@ -0,0 +1,10 @@
+set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
+
+If Not Exist %executable% (
+ mvn clean package
+)
+
+REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
+set ags=generate --artifact-id "kotlin-client-petstore-multiplatform" -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g kotlin --library multiplatform -o samples\client\petstore\kotlin-multiplatform
+
+java %JAVA_OPTS% -jar %executable% %ags%
diff --git a/docs/generators/kotlin.md b/docs/generators/kotlin.md
index ccf17bb6ecc..264842d4bb2 100644
--- a/docs/generators/kotlin.md
+++ b/docs/generators/kotlin.md
@@ -16,5 +16,6 @@ sidebar_label: kotlin
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |camelCase|
|serializationLibrary|What serialization library to use: 'moshi' (default), or 'gson'| |moshi|
|parcelizeModels|toggle "@Parcelize" for generated models| |null|
-|dateLibrary|Option. Date library to use|
- **string**
- String
- **java8**
- Java 8 native JSR310
- **threetenbp**
- Threetenbp
|java8|
+|dateLibrary|Option. Date library to use|- **string**
- String
- **java8**
- Java 8 native JSR310 (jvm only)
- **threetenbp**
- Threetenbp (jvm only)
|java8|
|collectionType|Option. Collection type to use|- **array**
- kotlin.Array
- **list**
- kotlin.collections.List
|array|
+|library|Library template (sub-template) to use|- **jvm**
- Platform: Java Virtual Machine. HTTP client: OkHttp 2.7.5. JSON processing: Gson 2.8.1.
- **multiplatform**
- Platform: Kotlin multiplatform. HTTP client: Ktor 1.2.4. JSON processing: Kotlinx Serialization: 0.12.0.
|jvm|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java
index adb1eba7cf1..a0a92778058 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java
@@ -19,21 +19,42 @@ package org.openapitools.codegen.languages;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenOperation;
+import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
import java.io.File;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
public class KotlinClientCodegen extends AbstractKotlinCodegen {
+ protected static final String VENDOR_EXTENSION_ESCAPED_NAME = "x-escapedName";
+
+ protected static final String JVM = "jvm";
+ protected static final String MULTIPLATFORM = "multiplatform";
+
public static final String DATE_LIBRARY = "dateLibrary";
public static final String COLLECTION_TYPE = "collectionType";
protected String dateLibrary = DateLibrary.JAVA8.value;
protected String collectionType = CollectionType.ARRAY.value;
+ // https://kotlinlang.org/docs/reference/grammar.html#Identifier
+ protected static final Pattern IDENTIFIER_PATTERN =
+ Pattern.compile("[\\p{Ll}\\p{Lm}\\p{Lo}\\p{Lt}\\p{Lu}\\p{Nl}_][\\p{Ll}\\p{Lm}\\p{Lo}\\p{Lt}\\p{Lu}\\p{Nl}\\p{Nd}_]*");
+
+ // https://kotlinlang.org/docs/reference/grammar.html#Identifier
+ protected static final String IDENTIFIER_REPLACEMENTS =
+ "[.;:/\\[\\]<>]";
+
public enum DateLibrary {
STRING("string"),
THREETENBP("threetenbp"),
@@ -81,9 +102,9 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
CliOption dateLibrary = new CliOption(DATE_LIBRARY, "Option. Date library to use");
Map dateOptions = new HashMap<>();
- dateOptions.put(DateLibrary.THREETENBP.value, "Threetenbp");
+ dateOptions.put(DateLibrary.THREETENBP.value, "Threetenbp (jvm only)");
dateOptions.put(DateLibrary.STRING.value, "String");
- dateOptions.put(DateLibrary.JAVA8.value, "Java 8 native JSR310");
+ dateOptions.put(DateLibrary.JAVA8.value, "Java 8 native JSR310 (jvm only)");
dateLibrary.setEnum(dateOptions);
dateLibrary.setDefault(this.dateLibrary);
cliOptions.add(dateLibrary);
@@ -95,6 +116,15 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
collectionType.setEnum(collectionOptions);
collectionType.setDefault(this.collectionType);
cliOptions.add(collectionType);
+
+ supportedLibraries.put(JVM, "Platform: Java Virtual Machine. HTTP client: OkHttp 2.7.5. JSON processing: Gson 2.8.1.");
+ supportedLibraries.put(MULTIPLATFORM, "Platform: Kotlin multiplatform. HTTP client: Ktor 1.2.4. JSON processing: Kotlinx Serialization: 0.12.0.");
+
+ CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "Library template (sub-template) to use");
+ libraryOption.setEnum(supportedLibraries);
+ libraryOption.setDefault(JVM);
+ cliOptions.add(libraryOption);
+ setLibrary(JVM);
}
public CodegenType getTag() {
@@ -121,10 +151,80 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
public void processOpts() {
super.processOpts();
+ if (MULTIPLATFORM.equals(getLibrary())) {
+ sourceFolder = "src/commonMain/kotlin";
+ }
+
+ // infrastructure destination folder
+ final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/");
+
+ // additional properties
if (additionalProperties.containsKey(DATE_LIBRARY)) {
setDateLibrary(additionalProperties.get(DATE_LIBRARY).toString());
}
+ // common (jvm/multiplatform) supporting files
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+ supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle"));
+ supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle"));
+ supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/ApiAbstractions.kt.mustache", infrastructureFolder, "ApiAbstractions.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/RequestMethod.kt.mustache", infrastructureFolder, "RequestMethod.kt"));
+
+ if (JVM.equals(getLibrary())) {
+ additionalProperties.put(JVM, true);
+
+ // jvm specific supporting files
+ supportingFiles.add(new SupportingFile("infrastructure/ApplicationDelegates.kt.mustache", infrastructureFolder, "ApplicationDelegates.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/Errors.kt.mustache", infrastructureFolder, "Errors.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/ResponseExtensions.kt.mustache", infrastructureFolder, "ResponseExtensions.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/Serializer.kt.mustache", infrastructureFolder, "Serializer.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/ApiInfrastructureResponse.kt.mustache", infrastructureFolder, "ApiInfrastructureResponse.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/ByteArrayAdapter.kt.mustache", infrastructureFolder, "ByteArrayAdapter.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/LocalDateAdapter.kt.mustache", infrastructureFolder, "LocalDateAdapter.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/LocalDateTimeAdapter.kt.mustache", infrastructureFolder, "LocalDateTimeAdapter.kt"));
+ supportingFiles.add(new SupportingFile("infrastructure/UUIDAdapter.kt.mustache", infrastructureFolder, "UUIDAdapter.kt"));
+
+ } else if (MULTIPLATFORM.equals(getLibrary())) {
+ additionalProperties.put(MULTIPLATFORM, true);
+ setDateLibrary(DateLibrary.STRING.value);
+
+ // multiplatform default includes
+ defaultIncludes.add("io.ktor.client.request.forms.InputProvider");
+
+ // multiplatform type mapping
+ typeMapping.put("number", "kotlin.Double");
+ typeMapping.put("file", "InputProvider");
+
+ // multiplatform import mapping
+ importMapping.put("BigDecimal", "kotlin.Double");
+ importMapping.put("UUID", "kotlin.String");
+ importMapping.put("URI", "kotlin.String");
+ importMapping.put("InputProvider", "io.ktor.client.request.forms.InputProvider");
+ importMapping.put("File", "io.ktor.client.request.forms.InputProvider");
+ importMapping.put("Timestamp", "kotlin.String");
+ importMapping.put("LocalDateTime", "kotlin.String");
+ importMapping.put("LocalDate", "kotlin.String");
+ importMapping.put("LocalTime", "kotlin.String");
+
+ // multiplatform specific supporting files
+ supportingFiles.add(new SupportingFile("infrastructure/HttpResponse.kt.mustache", infrastructureFolder, "HttpResponse.kt"));
+
+ // multiplatform specific testing files
+ final String testFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/");
+ supportingFiles.add(new SupportingFile("commonTest/coroutine.mustache", "src/commonTest/kotlin/util", "Coroutine.kt"));
+ supportingFiles.add(new SupportingFile("iosTest/coroutine.mustache", "src/iosTest/kotlin/util", "Coroutine.kt"));
+ supportingFiles.add(new SupportingFile("jvmTest/coroutine.mustache", "src/jvmTest/kotlin/util", "Coroutine.kt"));
+
+ // gradle wrapper supporting files
+ supportingFiles.add(new SupportingFile("gradlew.mustache", "", "gradlew"));
+ supportingFiles.add(new SupportingFile("gradlew.bat.mustache", "", "gradlew.bat"));
+ supportingFiles.add(new SupportingFile("gradle-wrapper.properties.mustache", "gradle.wrapper".replace(".", File.separator), "gradle-wrapper.properties"));
+ supportingFiles.add(new SupportingFile("gradle-wrapper.jar", "gradle.wrapper".replace(".", File.separator), "gradle-wrapper.jar"));
+ }
+
+ // date library processing
if (DateLibrary.THREETENBP.value.equals(dateLibrary)) {
additionalProperties.put(DateLibrary.THREETENBP.value, true);
typeMapping.put("date", "LocalDate");
@@ -151,25 +251,83 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
typeMapping.put("list", "kotlin.collections.List");
additionalProperties.put("isList", true);
}
+ }
- supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
- supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle"));
- supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle"));
+ @Override
+ public Map postProcessModels(Map objs) {
+ objs = super.postProcessModels(objs);
+ return postProcessModelsEscapeNames(objs);
+ }
- final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/");
+ @SuppressWarnings("unchecked")
+ private static Map postProcessModelsEscapeNames(Map objs) {
+ List