From 86ee02b9574508f9ea5ee07809fd98de8e437dfb Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 21 Feb 2018 09:26:38 +1100 Subject: [PATCH 1/9] Moving kotlin support code to swagger-codegen-generators --- .../languages/KotlinClientCodegen.java | 538 ------------------ 1 file changed, 538 deletions(-) delete mode 100644 modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java deleted file mode 100644 index 480cc1c0c74..00000000000 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java +++ /dev/null @@ -1,538 +0,0 @@ -package io.swagger.codegen.languages; - -import io.swagger.codegen.CliOption; -import io.swagger.codegen.CodegenConfig; -import io.swagger.codegen.CodegenConstants; -import io.swagger.codegen.CodegenType; -import io.swagger.codegen.DefaultCodegen; -import io.swagger.codegen.SupportingFile; -import io.swagger.v3.oas.models.media.ArraySchema; -import io.swagger.v3.oas.models.media.MapSchema; -import io.swagger.v3.oas.models.media.Schema; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - -public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig { - static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class); - - protected String groupId = "io.swagger"; - protected String artifactId = "kotlin-client"; - protected String artifactVersion = "1.0.0"; - protected String sourceFolder = "src/main/kotlin"; - protected String packageName = "io.swagger.client"; - protected String apiDocPath = "docs/"; - protected String modelDocPath = "docs/"; - protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase; - - /** - * Constructs an instance of `KotlinClientCodegen`. - */ - public KotlinClientCodegen() { - super(); - - outputFolder = "generated-code" + File.separator + "kotlin-client"; - modelTemplateFiles.put("model.mustache", ".kt"); - apiTemplateFiles.put("api.mustache", ".kt"); - modelDocTemplateFiles.put("model_doc.mustache", ".md"); - apiDocTemplateFiles.put("api_doc.mustache", ".md"); - embeddedTemplateDir = templateDir = "kotlin-client"; - apiPackage = packageName + ".apis"; - modelPackage = packageName + ".models"; - - languageSpecificPrimitives = new HashSet(Arrays.asList( - "kotlin.Byte", - "kotlin.Short", - "kotlin.Int", - "kotlin.Long", - "kotlin.Float", - "kotlin.Double", - "kotlin.Boolean", - "kotlin.Char", - "kotlin.String", - "kotlin.Array", - "kotlin.collections.List", - "kotlin.collections.Map", - "kotlin.collections.Set" - )); - - // this includes hard reserved words defined by https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java - // as well as keywords from https://kotlinlang.org/docs/reference/keyword-reference.html - reservedWords = new HashSet(Arrays.asList( - "abstract", - "annotation", - "as", - "break", - "case", - "catch", - "class", - "companion", - "const", - "constructor", - "continue", - "crossinline", - "data", - "delegate", - "do", - "else", - "enum", - "external", - "false", - "final", - "finally", - "for", - "fun", - "if", - "in", - "infix", - "init", - "inline", - "inner", - "interface", - "internal", - "is", - "it", - "lateinit", - "lazy", - "noinline", - "null", - "object", - "open", - "operator", - "out", - "override", - "package", - "private", - "protected", - "public", - "reified", - "return", - "sealed", - "super", - "suspend", - "tailrec", - "this", - "throw", - "true", - "try", - "typealias", - "typeof", - "val", - "var", - "vararg", - "when", - "while" - )); - - defaultIncludes = new HashSet(Arrays.asList( - "kotlin.Byte", - "kotlin.Short", - "kotlin.Int", - "kotlin.Long", - "kotlin.Float", - "kotlin.Double", - "kotlin.Boolean", - "kotlin.Char", - "kotlin.Array", - "kotlin.collections.List", - "kotlin.collections.Set", - "kotlin.collections.Map" - )); - - typeMapping = new HashMap(); - typeMapping.put("string", "kotlin.String"); - typeMapping.put("boolean", "kotlin.Boolean"); - typeMapping.put("integer", "kotlin.Int"); - typeMapping.put("float", "kotlin.Float"); - typeMapping.put("long", "kotlin.Long"); - typeMapping.put("double", "kotlin.Double"); - typeMapping.put("number", "java.math.BigDecimal"); - typeMapping.put("date-time", "java.time.LocalDateTime"); - typeMapping.put("date", "java.time.LocalDateTime"); - typeMapping.put("file", "java.io.File"); - typeMapping.put("array", "kotlin.Array"); - typeMapping.put("list", "kotlin.Array"); - typeMapping.put("map", "kotlin.collections.Map"); - typeMapping.put("object", "kotlin.Any"); - typeMapping.put("binary", "kotlin.Array"); - typeMapping.put("Date", "java.time.LocalDateTime"); - typeMapping.put("DateTime", "java.time.LocalDateTime"); - - instantiationTypes.put("array", "arrayOf"); - instantiationTypes.put("list", "arrayOf"); - instantiationTypes.put("map", "mapOf"); - - importMapping = new HashMap(); - importMapping.put("BigDecimal", "java.math.BigDecimal"); - importMapping.put("UUID", "java.util.UUID"); - importMapping.put("File", "java.io.File"); - importMapping.put("Date", "java.util.Date"); - importMapping.put("Timestamp", "java.sql.Timestamp"); - importMapping.put("DateTime", "java.time.LocalDateTime"); - importMapping.put("LocalDateTime", "java.time.LocalDateTime"); - importMapping.put("LocalDate", "java.time.LocalDate"); - importMapping.put("LocalTime", "java.time.LocalTime"); - - specialCharReplacements.put(";", "Semicolon"); - - cliOptions.clear(); - cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC).defaultValue(sourceFolder)); - cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Client package name (e.g. io.swagger).").defaultValue(this.packageName)); - cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, "Client package's organization (i.e. maven groupId).").defaultValue(groupId)); - cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, "Client artifact id (name of generated jar).").defaultValue(artifactId)); - cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, "Client package version.").defaultValue(artifactVersion)); - - CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC); - cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name())); - } - - public CodegenType getTag() { - return CodegenType.CLIENT; - } - - public String getName() { - return "kotlin"; - } - - public String getHelp() { - return "Generates a kotlin client."; - } - - public void setArtifactId(String artifactId) { - this.artifactId = artifactId; - } - - public void setArtifactVersion(String artifactVersion) { - this.artifactVersion = artifactVersion; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public void setPackageName(String packageName) { - this.packageName = packageName; - } - - public void setSourceFolder(String sourceFolder) { - this.sourceFolder = sourceFolder; - } - - @Override - public void processOpts() { - super.processOpts(); - - if (additionalProperties.containsKey(CodegenConstants.ENUM_PROPERTY_NAMING)) { - setEnumPropertyNaming((String) additionalProperties.get(CodegenConstants.ENUM_PROPERTY_NAMING)); - } - - if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { - this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); - } else { - additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder); - } - - if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { - this.setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); - if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) - this.setModelPackage(packageName + ".models"); - if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) - this.setApiPackage(packageName + ".apis"); - } else { - additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); - } - - if(additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) { - this.setArtifactId((String) additionalProperties.get(CodegenConstants.ARTIFACT_ID)); - } else { - additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); - } - - if(additionalProperties.containsKey(CodegenConstants.GROUP_ID)) { - this.setGroupId((String) additionalProperties.get(CodegenConstants.GROUP_ID)); - } else { - additionalProperties.put(CodegenConstants.GROUP_ID, groupId); - } - - if(additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) { - this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION)); - } else { - additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); - } - - if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { - LOGGER.warn(CodegenConstants.INVOKER_PACKAGE + " with " + this.getName() + " generator is ignored. Use " + CodegenConstants.PACKAGE_NAME + "."); - } - - additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage()); - additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage()); - - additionalProperties.put("apiDocPath", apiDocPath); - additionalProperties.put("modelDocPath", modelDocPath); - - 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")); - - final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/"); - - 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/ApiInfrastructureResponse.kt.mustache", infrastructureFolder, "ApiInfrastructureResponse.kt")); - supportingFiles.add(new SupportingFile("infrastructure/ApplicationDelegates.kt.mustache", infrastructureFolder, "ApplicationDelegates.kt")); - supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt")); - supportingFiles.add(new SupportingFile("infrastructure/RequestMethod.kt.mustache", infrastructureFolder, "RequestMethod.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/Errors.kt.mustache", infrastructureFolder, "Errors.kt")); - } - - /** - * Sets the naming convention for Kotlin enum properties - * - * @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link CodegenConstants.ENUM_PROPERTY_NAMING_TYPE} - */ - public void setEnumPropertyNaming(final String enumPropertyNamingType) { - try { - this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType); - } catch (IllegalArgumentException ex) { - StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:"); - for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) { - sb.append("\n ").append(t.name()); - } - throw new RuntimeException(sb.toString()); - } - } - - public CodegenConstants.ENUM_PROPERTY_NAMING_TYPE getEnumPropertyNaming() { - return this.enumPropertyNaming; - } - - @Override - public String escapeUnsafeCharacters(String input) { - return input.replace("*/", "*_/").replace("/*", "/_*"); - } - - @Override - public String escapeQuotationMark(String input) { - // remove " to avoid code injection - return input.replace("\"", ""); - } - - @Override - public String apiDocFileFolder() { - return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); - } - - @Override - public String apiFileFolder() { - return outputFolder + File.separator + sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar); - } - - @Override - public String modelDocFileFolder() { - return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); - } - - - @Override - public String modelFileFolder() { - return outputFolder + File.separator + sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar); - } - - @Override - public String escapeReservedWord(String name) { - // TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping). - return String.format("`%s`", name); - } - - /** - * Output the proper model name (capitalized). - * In case the name belongs to the TypeSystem it won't be renamed. - * - * @param name the name of the model - * @return capitalized model name - */ - @Override - public String toModelName(final String name) { - // Allow for explicitly configured kotlin.* and java.* types - if (name.startsWith("kotlin.") || name.startsWith("java.")) { - return name; - } - - // If importMapping contains name, assume this is a legitimate model name. - if (importMapping.containsKey(name)) { - return importMapping.get(name); - } - - String modifiedName = name.replaceAll("\\.", ""); - modifiedName = sanitizeKotlinSpecificNames(modifiedName); - - if (reservedWords.contains(modifiedName)) { - modifiedName = escapeReservedWord(modifiedName); - } - - return titleCase(modifiedName); - } - - /** - * returns the swagger type for the property - * - * @param propertySchema Schema property object - * @return string presentation of the type - **/ - @Override - public String getSchemaType(Schema propertySchema) { - String schemaType = super.getSchemaType(propertySchema); - String type = null; - if (typeMapping.containsKey(schemaType)) { - type = typeMapping.get(schemaType); - if (languageSpecificPrimitives.contains(type)) { - return toModelName(type); - } - } else { - type = schemaType; - } - return toModelName(type); - } - - /** - * Output the type declaration of the property - * - * @param propertySchema Schema Property object - * @return a string presentation of the property type - */ - - @Override - public String getTypeDeclaration(Schema propertySchema) { - if (propertySchema instanceof ArraySchema) { - Schema inner = ((ArraySchema) propertySchema).getItems(); - return String.format("%s<%s>", getSchemaType(propertySchema), getTypeDeclaration(inner)); - } else if (propertySchema instanceof MapSchema && hasSchemaProperties(propertySchema)) { - Schema inner = (Schema) propertySchema.getAdditionalProperties(); - return String.format("%s", getSchemaType(propertySchema), getTypeDeclaration(inner)); - } - return super.getTypeDeclaration(propertySchema); - } - - /** - * Check the type to see if it needs import the library/module/package - * - * @param type name of the type - * @return true if the library/module/package of the corresponding type needs to be imported - */ - @Override - protected boolean needToImport(String type) { - // provides extra protection against improperly trying to import language primitives and java types - boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); - return imports; - } - - /** - * Return the fully-qualified "Model" name for import - * - * @param name the name of the "Model" - * @return the fully-qualified "Model" name for import - */ - @Override - public String toModelImport(String name) { - // toModelImport is called while processing operations, but DefaultCodegen doesn't - // define imports correctly with fully qualified primitives and models as defined in this generator. - if(needToImport(name)) { - return super.toModelImport(name); - } - - return name; - } - - @Override - public Map postProcessModels(Map objs) { - return postProcessModelsEnum(super.postProcessModels(objs)); - } - - /** - * Return the sanitized variable name for enum - * - * @param value enum variable name - * @param datatype data type - * @return the sanitized variable name for enum - */ - @Override - public String toEnumVarName(String value, String datatype) { - String modified; - if (value.length() == 0) { - modified = "EMPTY"; - } else { - modified = value; - modified = sanitizeKotlinSpecificNames(modified); - } - - switch (getEnumPropertyNaming()) { - case original: - // NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped. - modified = value; - break; - case camelCase: - // NOTE: Removes hyphens and underscores - modified = camelize(modified, true); - break; - case PascalCase: - // NOTE: Removes hyphens and underscores - String result = camelize(modified); - modified = titleCase(result); - break; - case snake_case: - // NOTE: Removes hyphens - modified = underscore(modified); - break; - case UPPERCASE: - modified = modified.toUpperCase(); - break; - } - - if (reservedWords.contains(modified)) { - return escapeReservedWord(modified); - } - - return modified; - } - - private String titleCase(final String input) { - return input.substring(0, 1).toUpperCase() + input.substring(1); - } - - /** - * Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}. - * - * @param name string to be sanitize - * @return sanitized string - */ - private String sanitizeKotlinSpecificNames(final String name) { - String word = name; - for (Map.Entry specialCharacters : specialCharReplacements.entrySet()) { - // Underscore is the only special character we'll allow - if (!specialCharacters.getKey().equals("_")) { - word = word.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue()); - } - } - - // Fallback, replace unknowns with underscore. - word = word.replaceAll("\\W+", "_"); - if (word.matches("\\d.*")) { - word = "_" + word; - } - - // _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count. - if (word.matches("^_*$")) { - word = word.replaceAll("\\Q_\\E", "Underscore"); - } - - return word; - } -} From ee834db614a7f0cf8ce79c0714e57ee2f4d16e7f Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 21 Feb 2018 10:02:28 +1100 Subject: [PATCH 2/9] Moving kotlin support code to swagger-codegen-generators --- .../services/io.swagger.codegen.CodegenConfig | 3 +- .../kotlin/KotlinClientCodegenModelTest.java | 207 ------------------ .../KotlinClientCodegenOptionsTest.java | 44 ---- .../KotlinClientCodegenOptionsProvider.java | 40 ---- 4 files changed, 2 insertions(+), 292 deletions(-) delete mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java delete mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenOptionsTest.java delete mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java diff --git a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig index d3a53d21c87..bb0123b8113 100644 --- a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig +++ b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig @@ -42,7 +42,8 @@ io.swagger.codegen.languages.JavaResteasyServerCodegen io.swagger.codegen.languages.JavascriptClientCodegen io.swagger.codegen.languages.JavascriptClosureAngularClientCodegen io.swagger.codegen.languages.JavaVertXServerCodegen -io.swagger.codegen.languages.KotlinClientCodegen +io.swagger.codegen.languages.kotlin.KotlinClientCodegen +io.swagger.codegen.languages.kotlin.KotlinServerCodegen io.swagger.codegen.languages.LuaClientCodegen io.swagger.codegen.languages.LumenServerCodegen io.swagger.codegen.languages.NancyFXServerCodegen diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java deleted file mode 100644 index 9a490328c3e..00000000000 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java +++ /dev/null @@ -1,207 +0,0 @@ -package io.swagger.codegen.kotlin; - -import io.swagger.codegen.CodegenConstants; -import io.swagger.codegen.CodegenModel; -import io.swagger.codegen.CodegenProperty; -import io.swagger.codegen.DefaultCodegen; -import io.swagger.codegen.languages.KotlinClientCodegen; -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.Schema; -import io.swagger.v3.oas.models.media.StringSchema; -import io.swagger.v3.parser.util.SchemaTypeUtil; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import static io.swagger.codegen.languages.helpers.ExtensionHelper.getBooleanValue; - -@SuppressWarnings("static-method") -public class KotlinClientCodegenModelTest { - - private Schema getArrayTestModel() { - return new Schema() - .type("object") - .description("a sample model") - .addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)) - .addProperties("examples", new ArraySchema().items(new StringSchema())) - .addRequiredItem("id"); - } - - private Schema getSimpleModel() { - return new Schema() - .type("object") - .description("a sample model") - .addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)) - .addProperties("name", new StringSchema()) - .addProperties("createdAt", new DateTimeSchema()) - .addRequiredItem("id") - .addRequiredItem("name"); - } - - private Schema getMapModel() { - return new Schema() - .type("object") - .description("a sample model") - .addProperties("mapping", new MapSchema() - .additionalProperties(new StringSchema())) - .addRequiredItem("id"); - } - - private Schema getComplexModel() { - return new Schema() - .type("object") - .description("a sample model") - .addProperties("child", new Schema().$ref("#/components/schemas/Child")); - } - - @Test(enabled = false,description = "convert a simple model") - public void simpleModelTest() { - final Schema model = getSimpleModel(); - final DefaultCodegen codegen = new KotlinClientCodegen(); - - final CodegenModel cm = codegen.fromModel("sample", model); - - Assert.assertEquals(cm.name, "sample"); - Assert.assertEquals(cm.classname, "Sample"); - Assert.assertEquals(cm.description, "a sample model"); - Assert.assertEquals(cm.vars.size(), 3); - - final CodegenProperty property1 = cm.vars.get(0); - Assert.assertEquals(property1.baseName, "id"); - Assert.assertEquals(property1.datatype, "kotlin.Long"); - Assert.assertEquals(property1.name, "id"); - Assert.assertEquals(property1.defaultValue, "null"); - Assert.assertEquals(property1.baseType, "kotlin.Long"); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.HAS_MORE_EXT_NAME)); - Assert.assertTrue(property1.required); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); - - final CodegenProperty property2 = cm.vars.get(1); - Assert.assertEquals(property2.baseName, "name"); - Assert.assertEquals(property2.datatype, "kotlin.String"); - Assert.assertEquals(property2.name, "name"); - Assert.assertEquals(property2.defaultValue, "null"); - Assert.assertEquals(property2.baseType, "kotlin.String"); - Assert.assertTrue(getBooleanValue(property2, CodegenConstants.HAS_MORE_EXT_NAME)); - Assert.assertTrue(property2.required); - Assert.assertTrue(getBooleanValue(property2, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); - Assert.assertTrue(getBooleanValue(property2, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); - - final CodegenProperty property3 = cm.vars.get(2); - Assert.assertEquals(property3.baseName, "createdAt"); - Assert.assertEquals(property3.datatype, "java.time.LocalDateTime"); - Assert.assertEquals(property3.name, "createdAt"); - Assert.assertEquals(property3.defaultValue, "null"); - Assert.assertEquals(property3.baseType, "java.time.LocalDateTime"); - Assert.assertFalse(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); - Assert.assertFalse(property3.required); - Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); - } - - @Test(enabled = false, description = "convert a model with array property to default kotlin.Array") - public void arrayPropertyTest() { - final Schema model = getArrayTestModel(); - - final DefaultCodegen codegen = new KotlinClientCodegen(); - final CodegenModel generated = codegen.fromModel("sample", model); - - Assert.assertEquals(generated.name, "sample"); - Assert.assertEquals(generated.classname, "Sample"); - Assert.assertEquals(generated.description, "a sample model"); - Assert.assertEquals(generated.vars.size(), 2); - - final CodegenProperty property = generated.vars.get(1); - Assert.assertEquals(property.baseName, "examples"); - Assert.assertEquals(property.getter, "getExamples"); - Assert.assertEquals(property.setter, "setExamples"); - Assert.assertEquals(property.datatype, "kotlin.Array"); - Assert.assertEquals(property.name, "examples"); - Assert.assertEquals(property.defaultValue, "null"); - Assert.assertEquals(property.baseType, "kotlin.Array"); - Assert.assertEquals(property.containerType, "array"); - Assert.assertFalse(property.required); - Assert.assertTrue(getBooleanValue(property, CodegenConstants.IS_CONTAINER_EXT_NAME)); - } - @Test(enabled = false, description = "convert a model with a map property") - public void mapPropertyTest() { - final Schema model = getMapModel(); - final DefaultCodegen codegen = new KotlinClientCodegen(); - final CodegenModel cm = codegen.fromModel("sample", model); - - Assert.assertEquals(cm.name, "sample"); - Assert.assertEquals(cm.classname, "Sample"); - Assert.assertEquals(cm.description, "a sample model"); - Assert.assertEquals(cm.vars.size(), 1); - - final CodegenProperty property1 = cm.vars.get(0); - Assert.assertEquals(property1.baseName, "mapping"); - Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); - Assert.assertEquals(property1.name, "mapping"); - Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); - Assert.assertEquals(property1.containerType, "map"); - Assert.assertFalse(property1.required); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); - } - - @Test(enabled = false, description = "convert a model with complex property") - public void complexPropertyTest() { - final Schema model = getComplexModel(); - final DefaultCodegen codegen = new KotlinClientCodegen(); - final CodegenModel cm = codegen.fromModel("sample", model); - - Assert.assertEquals(cm.name, "sample"); - Assert.assertEquals(cm.classname, "Sample"); - Assert.assertEquals(cm.description, "a sample model"); - Assert.assertEquals(cm.vars.size(), 1); - - final CodegenProperty property1 = cm.vars.get(0); - Assert.assertEquals(property1.baseName, "child"); - Assert.assertEquals(property1.datatype, "Child"); - Assert.assertEquals(property1.name, "child"); - Assert.assertEquals(property1.baseType, "Child"); - Assert.assertFalse(property1.required); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); - } - - @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 createOptions() { - ImmutableMap.Builder builder = new ImmutableMap.Builder(); - return builder - .put(CodegenConstants.PACKAGE_NAME, PACKAGE_NAME_VALUE) - .put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE) - .put(CodegenConstants.ARTIFACT_ID, ARTIFACT_ID) - .put(CodegenConstants.GROUP_ID, GROUP_ID) - .put(CodegenConstants.SOURCE_FOLDER, SOURCE_FOLDER) - .put(CodegenConstants.ENUM_PROPERTY_NAMING, ENUM_PROPERTY_NAMING) - .build(); - } - - @Override - public boolean isServer() { - return false; - } -} - From d5203542b20d92a7911ed12ea33b3c873491feb6 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 08:42:50 +1100 Subject: [PATCH 3/9] Revert "Moving kotlin support code to swagger-codegen-generators" This reverts commit 86ee02b9574508f9ea5ee07809fd98de8e437dfb. --- .../languages/KotlinClientCodegen.java | 538 ++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java new file mode 100644 index 00000000000..480cc1c0c74 --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java @@ -0,0 +1,538 @@ +package io.swagger.codegen.languages; + +import io.swagger.codegen.CliOption; +import io.swagger.codegen.CodegenConfig; +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.CodegenType; +import io.swagger.codegen.DefaultCodegen; +import io.swagger.codegen.SupportingFile; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.Schema; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig { + static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class); + + protected String groupId = "io.swagger"; + protected String artifactId = "kotlin-client"; + protected String artifactVersion = "1.0.0"; + protected String sourceFolder = "src/main/kotlin"; + protected String packageName = "io.swagger.client"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase; + + /** + * Constructs an instance of `KotlinClientCodegen`. + */ + public KotlinClientCodegen() { + super(); + + outputFolder = "generated-code" + File.separator + "kotlin-client"; + modelTemplateFiles.put("model.mustache", ".kt"); + apiTemplateFiles.put("api.mustache", ".kt"); + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + embeddedTemplateDir = templateDir = "kotlin-client"; + apiPackage = packageName + ".apis"; + modelPackage = packageName + ".models"; + + languageSpecificPrimitives = new HashSet(Arrays.asList( + "kotlin.Byte", + "kotlin.Short", + "kotlin.Int", + "kotlin.Long", + "kotlin.Float", + "kotlin.Double", + "kotlin.Boolean", + "kotlin.Char", + "kotlin.String", + "kotlin.Array", + "kotlin.collections.List", + "kotlin.collections.Map", + "kotlin.collections.Set" + )); + + // this includes hard reserved words defined by https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java + // as well as keywords from https://kotlinlang.org/docs/reference/keyword-reference.html + reservedWords = new HashSet(Arrays.asList( + "abstract", + "annotation", + "as", + "break", + "case", + "catch", + "class", + "companion", + "const", + "constructor", + "continue", + "crossinline", + "data", + "delegate", + "do", + "else", + "enum", + "external", + "false", + "final", + "finally", + "for", + "fun", + "if", + "in", + "infix", + "init", + "inline", + "inner", + "interface", + "internal", + "is", + "it", + "lateinit", + "lazy", + "noinline", + "null", + "object", + "open", + "operator", + "out", + "override", + "package", + "private", + "protected", + "public", + "reified", + "return", + "sealed", + "super", + "suspend", + "tailrec", + "this", + "throw", + "true", + "try", + "typealias", + "typeof", + "val", + "var", + "vararg", + "when", + "while" + )); + + defaultIncludes = new HashSet(Arrays.asList( + "kotlin.Byte", + "kotlin.Short", + "kotlin.Int", + "kotlin.Long", + "kotlin.Float", + "kotlin.Double", + "kotlin.Boolean", + "kotlin.Char", + "kotlin.Array", + "kotlin.collections.List", + "kotlin.collections.Set", + "kotlin.collections.Map" + )); + + typeMapping = new HashMap(); + typeMapping.put("string", "kotlin.String"); + typeMapping.put("boolean", "kotlin.Boolean"); + typeMapping.put("integer", "kotlin.Int"); + typeMapping.put("float", "kotlin.Float"); + typeMapping.put("long", "kotlin.Long"); + typeMapping.put("double", "kotlin.Double"); + typeMapping.put("number", "java.math.BigDecimal"); + typeMapping.put("date-time", "java.time.LocalDateTime"); + typeMapping.put("date", "java.time.LocalDateTime"); + typeMapping.put("file", "java.io.File"); + typeMapping.put("array", "kotlin.Array"); + typeMapping.put("list", "kotlin.Array"); + typeMapping.put("map", "kotlin.collections.Map"); + typeMapping.put("object", "kotlin.Any"); + typeMapping.put("binary", "kotlin.Array"); + typeMapping.put("Date", "java.time.LocalDateTime"); + typeMapping.put("DateTime", "java.time.LocalDateTime"); + + instantiationTypes.put("array", "arrayOf"); + instantiationTypes.put("list", "arrayOf"); + instantiationTypes.put("map", "mapOf"); + + importMapping = new HashMap(); + importMapping.put("BigDecimal", "java.math.BigDecimal"); + importMapping.put("UUID", "java.util.UUID"); + importMapping.put("File", "java.io.File"); + importMapping.put("Date", "java.util.Date"); + importMapping.put("Timestamp", "java.sql.Timestamp"); + importMapping.put("DateTime", "java.time.LocalDateTime"); + importMapping.put("LocalDateTime", "java.time.LocalDateTime"); + importMapping.put("LocalDate", "java.time.LocalDate"); + importMapping.put("LocalTime", "java.time.LocalTime"); + + specialCharReplacements.put(";", "Semicolon"); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC).defaultValue(sourceFolder)); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Client package name (e.g. io.swagger).").defaultValue(this.packageName)); + cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, "Client package's organization (i.e. maven groupId).").defaultValue(groupId)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, "Client artifact id (name of generated jar).").defaultValue(artifactId)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, "Client package version.").defaultValue(artifactVersion)); + + CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC); + cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name())); + } + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "kotlin"; + } + + public String getHelp() { + return "Generates a kotlin client."; + } + + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public void setArtifactVersion(String artifactVersion) { + this.artifactVersion = artifactVersion; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.ENUM_PROPERTY_NAMING)) { + setEnumPropertyNaming((String) additionalProperties.get(CodegenConstants.ENUM_PROPERTY_NAMING)); + } + + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } else { + additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + this.setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) + this.setModelPackage(packageName + ".models"); + if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) + this.setApiPackage(packageName + ".apis"); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + } + + if(additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) { + this.setArtifactId((String) additionalProperties.get(CodegenConstants.ARTIFACT_ID)); + } else { + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + } + + if(additionalProperties.containsKey(CodegenConstants.GROUP_ID)) { + this.setGroupId((String) additionalProperties.get(CodegenConstants.GROUP_ID)); + } else { + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + } + + if(additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) { + this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION)); + } else { + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + } + + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + LOGGER.warn(CodegenConstants.INVOKER_PACKAGE + " with " + this.getName() + " generator is ignored. Use " + CodegenConstants.PACKAGE_NAME + "."); + } + + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage()); + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage()); + + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + 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")); + + final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/"); + + 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/ApiInfrastructureResponse.kt.mustache", infrastructureFolder, "ApiInfrastructureResponse.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ApplicationDelegates.kt.mustache", infrastructureFolder, "ApplicationDelegates.kt")); + supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt")); + supportingFiles.add(new SupportingFile("infrastructure/RequestMethod.kt.mustache", infrastructureFolder, "RequestMethod.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/Errors.kt.mustache", infrastructureFolder, "Errors.kt")); + } + + /** + * Sets the naming convention for Kotlin enum properties + * + * @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link CodegenConstants.ENUM_PROPERTY_NAMING_TYPE} + */ + public void setEnumPropertyNaming(final String enumPropertyNamingType) { + try { + this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType); + } catch (IllegalArgumentException ex) { + StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:"); + for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) { + sb.append("\n ").append(t.name()); + } + throw new RuntimeException(sb.toString()); + } + } + + public CodegenConstants.ENUM_PROPERTY_NAMING_TYPE getEnumPropertyNaming() { + return this.enumPropertyNaming; + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); + } + + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar); + } + + @Override + public String escapeReservedWord(String name) { + // TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping). + return String.format("`%s`", name); + } + + /** + * Output the proper model name (capitalized). + * In case the name belongs to the TypeSystem it won't be renamed. + * + * @param name the name of the model + * @return capitalized model name + */ + @Override + public String toModelName(final String name) { + // Allow for explicitly configured kotlin.* and java.* types + if (name.startsWith("kotlin.") || name.startsWith("java.")) { + return name; + } + + // If importMapping contains name, assume this is a legitimate model name. + if (importMapping.containsKey(name)) { + return importMapping.get(name); + } + + String modifiedName = name.replaceAll("\\.", ""); + modifiedName = sanitizeKotlinSpecificNames(modifiedName); + + if (reservedWords.contains(modifiedName)) { + modifiedName = escapeReservedWord(modifiedName); + } + + return titleCase(modifiedName); + } + + /** + * returns the swagger type for the property + * + * @param propertySchema Schema property object + * @return string presentation of the type + **/ + @Override + public String getSchemaType(Schema propertySchema) { + String schemaType = super.getSchemaType(propertySchema); + String type = null; + if (typeMapping.containsKey(schemaType)) { + type = typeMapping.get(schemaType); + if (languageSpecificPrimitives.contains(type)) { + return toModelName(type); + } + } else { + type = schemaType; + } + return toModelName(type); + } + + /** + * Output the type declaration of the property + * + * @param propertySchema Schema Property object + * @return a string presentation of the property type + */ + + @Override + public String getTypeDeclaration(Schema propertySchema) { + if (propertySchema instanceof ArraySchema) { + Schema inner = ((ArraySchema) propertySchema).getItems(); + return String.format("%s<%s>", getSchemaType(propertySchema), getTypeDeclaration(inner)); + } else if (propertySchema instanceof MapSchema && hasSchemaProperties(propertySchema)) { + Schema inner = (Schema) propertySchema.getAdditionalProperties(); + return String.format("%s", getSchemaType(propertySchema), getTypeDeclaration(inner)); + } + return super.getTypeDeclaration(propertySchema); + } + + /** + * Check the type to see if it needs import the library/module/package + * + * @param type name of the type + * @return true if the library/module/package of the corresponding type needs to be imported + */ + @Override + protected boolean needToImport(String type) { + // provides extra protection against improperly trying to import language primitives and java types + boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); + return imports; + } + + /** + * Return the fully-qualified "Model" name for import + * + * @param name the name of the "Model" + * @return the fully-qualified "Model" name for import + */ + @Override + public String toModelImport(String name) { + // toModelImport is called while processing operations, but DefaultCodegen doesn't + // define imports correctly with fully qualified primitives and models as defined in this generator. + if(needToImport(name)) { + return super.toModelImport(name); + } + + return name; + } + + @Override + public Map postProcessModels(Map objs) { + return postProcessModelsEnum(super.postProcessModels(objs)); + } + + /** + * Return the sanitized variable name for enum + * + * @param value enum variable name + * @param datatype data type + * @return the sanitized variable name for enum + */ + @Override + public String toEnumVarName(String value, String datatype) { + String modified; + if (value.length() == 0) { + modified = "EMPTY"; + } else { + modified = value; + modified = sanitizeKotlinSpecificNames(modified); + } + + switch (getEnumPropertyNaming()) { + case original: + // NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped. + modified = value; + break; + case camelCase: + // NOTE: Removes hyphens and underscores + modified = camelize(modified, true); + break; + case PascalCase: + // NOTE: Removes hyphens and underscores + String result = camelize(modified); + modified = titleCase(result); + break; + case snake_case: + // NOTE: Removes hyphens + modified = underscore(modified); + break; + case UPPERCASE: + modified = modified.toUpperCase(); + break; + } + + if (reservedWords.contains(modified)) { + return escapeReservedWord(modified); + } + + return modified; + } + + private String titleCase(final String input) { + return input.substring(0, 1).toUpperCase() + input.substring(1); + } + + /** + * Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}. + * + * @param name string to be sanitize + * @return sanitized string + */ + private String sanitizeKotlinSpecificNames(final String name) { + String word = name; + for (Map.Entry specialCharacters : specialCharReplacements.entrySet()) { + // Underscore is the only special character we'll allow + if (!specialCharacters.getKey().equals("_")) { + word = word.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue()); + } + } + + // Fallback, replace unknowns with underscore. + word = word.replaceAll("\\W+", "_"); + if (word.matches("\\d.*")) { + word = "_" + word; + } + + // _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count. + if (word.matches("^_*$")) { + word = word.replaceAll("\\Q_\\E", "Underscore"); + } + + return word; + } +} From b82dfe313c13d35b9ee978c12f174fb0cc0b6997 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 08:46:07 +1100 Subject: [PATCH 4/9] Put back old class and mark as deprecated --- .../io/swagger/codegen/languages/KotlinClientCodegen.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java index 480cc1c0c74..682a7e8b9d8 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java @@ -18,6 +18,11 @@ import java.util.HashSet; import java.util.Map; +/** + * new version of this class can be found on: https://github.com/swagger-api/swagger-codegen-generators + * @deprecated use io.swagger.codegen.languages.kotlin.KotlinClientCodegen instead. + */ +@Deprecated public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig { static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class); From a90dad194eadd2a0568987205af555b0047b1ef4 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 11:13:41 +1100 Subject: [PATCH 5/9] Remove kotlin classes from service registry --- .../META-INF/services/io.swagger.codegen.CodegenConfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig index 63a96846b9b..18ff8702960 100644 --- a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig +++ b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig @@ -40,8 +40,6 @@ io.swagger.codegen.languages.JavaResteasyServerCodegen io.swagger.codegen.languages.JavascriptClientCodegen io.swagger.codegen.languages.JavascriptClosureAngularClientCodegen io.swagger.codegen.languages.JavaVertXServerCodegen -io.swagger.codegen.languages.kotlin.KotlinClientCodegen -io.swagger.codegen.languages.kotlin.KotlinServerCodegen io.swagger.codegen.languages.LuaClientCodegen io.swagger.codegen.languages.LumenServerCodegen io.swagger.codegen.languages.NancyFXServerCodegen From 3992702a2f815a0bf853956b7de7038a1dff28e6 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 11:15:38 +1100 Subject: [PATCH 6/9] Revert "Moving kotlin support code to swagger-codegen-generators" This reverts commit ee834db614a7f0cf8ce79c0714e57ee2f4d16e7f. Revert test removal --- .../kotlin/KotlinClientCodegenModelTest.java | 207 ++++++++++++++++++ .../KotlinClientCodegenOptionsTest.java | 44 ++++ .../KotlinClientCodegenOptionsProvider.java | 40 ++++ 3 files changed, 291 insertions(+) create mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java create mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenOptionsTest.java create mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java new file mode 100644 index 00000000000..9a490328c3e --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java @@ -0,0 +1,207 @@ +package io.swagger.codegen.kotlin; + +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.CodegenModel; +import io.swagger.codegen.CodegenProperty; +import io.swagger.codegen.DefaultCodegen; +import io.swagger.codegen.languages.KotlinClientCodegen; +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.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static io.swagger.codegen.languages.helpers.ExtensionHelper.getBooleanValue; + +@SuppressWarnings("static-method") +public class KotlinClientCodegenModelTest { + + private Schema getArrayTestModel() { + return new Schema() + .type("object") + .description("a sample model") + .addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)) + .addProperties("examples", new ArraySchema().items(new StringSchema())) + .addRequiredItem("id"); + } + + private Schema getSimpleModel() { + return new Schema() + .type("object") + .description("a sample model") + .addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)) + .addProperties("name", new StringSchema()) + .addProperties("createdAt", new DateTimeSchema()) + .addRequiredItem("id") + .addRequiredItem("name"); + } + + private Schema getMapModel() { + return new Schema() + .type("object") + .description("a sample model") + .addProperties("mapping", new MapSchema() + .additionalProperties(new StringSchema())) + .addRequiredItem("id"); + } + + private Schema getComplexModel() { + return new Schema() + .type("object") + .description("a sample model") + .addProperties("child", new Schema().$ref("#/components/schemas/Child")); + } + + @Test(enabled = false,description = "convert a simple model") + public void simpleModelTest() { + final Schema model = getSimpleModel(); + final DefaultCodegen codegen = new KotlinClientCodegen(); + + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 3); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.datatype, "kotlin.Long"); + Assert.assertEquals(property1.name, "id"); + Assert.assertEquals(property1.defaultValue, "null"); + Assert.assertEquals(property1.baseType, "kotlin.Long"); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertTrue(property1.required); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + + final CodegenProperty property2 = cm.vars.get(1); + Assert.assertEquals(property2.baseName, "name"); + Assert.assertEquals(property2.datatype, "kotlin.String"); + Assert.assertEquals(property2.name, "name"); + Assert.assertEquals(property2.defaultValue, "null"); + Assert.assertEquals(property2.baseType, "kotlin.String"); + Assert.assertTrue(getBooleanValue(property2, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertTrue(property2.required); + Assert.assertTrue(getBooleanValue(property2, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property2, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.datatype, "java.time.LocalDateTime"); + Assert.assertEquals(property3.name, "createdAt"); + Assert.assertEquals(property3.defaultValue, "null"); + Assert.assertEquals(property3.baseType, "java.time.LocalDateTime"); + Assert.assertFalse(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(property3.required); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + } + + @Test(enabled = false, description = "convert a model with array property to default kotlin.Array") + public void arrayPropertyTest() { + final Schema model = getArrayTestModel(); + + final DefaultCodegen codegen = new KotlinClientCodegen(); + final CodegenModel generated = codegen.fromModel("sample", model); + + Assert.assertEquals(generated.name, "sample"); + Assert.assertEquals(generated.classname, "Sample"); + Assert.assertEquals(generated.description, "a sample model"); + Assert.assertEquals(generated.vars.size(), 2); + + final CodegenProperty property = generated.vars.get(1); + Assert.assertEquals(property.baseName, "examples"); + Assert.assertEquals(property.getter, "getExamples"); + Assert.assertEquals(property.setter, "setExamples"); + Assert.assertEquals(property.datatype, "kotlin.Array"); + Assert.assertEquals(property.name, "examples"); + Assert.assertEquals(property.defaultValue, "null"); + Assert.assertEquals(property.baseType, "kotlin.Array"); + Assert.assertEquals(property.containerType, "array"); + Assert.assertFalse(property.required); + Assert.assertTrue(getBooleanValue(property, CodegenConstants.IS_CONTAINER_EXT_NAME)); + } + @Test(enabled = false, description = "convert a model with a map property") + public void mapPropertyTest() { + final Schema model = getMapModel(); + final DefaultCodegen codegen = new KotlinClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "mapping"); + Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); + Assert.assertEquals(property1.name, "mapping"); + Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); + Assert.assertEquals(property1.containerType, "map"); + Assert.assertFalse(property1.required); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + } + + @Test(enabled = false, description = "convert a model with complex property") + public void complexPropertyTest() { + final Schema model = getComplexModel(); + final DefaultCodegen codegen = new KotlinClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "child"); + Assert.assertEquals(property1.datatype, "Child"); + Assert.assertEquals(property1.name, "child"); + Assert.assertEquals(property1.baseType, "Child"); + Assert.assertFalse(property1.required); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + } + + @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 createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder + .put(CodegenConstants.PACKAGE_NAME, PACKAGE_NAME_VALUE) + .put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE) + .put(CodegenConstants.ARTIFACT_ID, ARTIFACT_ID) + .put(CodegenConstants.GROUP_ID, GROUP_ID) + .put(CodegenConstants.SOURCE_FOLDER, SOURCE_FOLDER) + .put(CodegenConstants.ENUM_PROPERTY_NAMING, ENUM_PROPERTY_NAMING) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} + From 9ca8242a229c5d0366d9fce5b11884bcd749a550 Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 28 Feb 2018 09:06:46 +1100 Subject: [PATCH 7/9] Remove template path from petstore script --- bin/kotlin-client-petstore.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/kotlin-client-petstore.sh b/bin/kotlin-client-petstore.sh index f2e558e2466..d3eba9a2ae7 100755 --- a/bin/kotlin-client-petstore.sh +++ b/bin/kotlin-client-petstore.sh @@ -26,6 +26,6 @@ fi # if you've executed sbt assembly previously it will use that instead. export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" -ags="generate -t modules/swagger-codegen/src/main/resources/kotlin-client -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l kotlin --artifact-id kotlin-petstore-client -o samples/client/petstore/kotlin $@" +ags="generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l kotlin --artifact-id kotlin-petstore-client -o samples/client/petstore/kotlin $@" java ${JAVA_OPTS} -jar ${executable} ${ags} From 896c6915dcd33df3fdb6f1d01c4da1c48aa7bc1c Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 5 Mar 2018 17:10:54 +1100 Subject: [PATCH 8/9] Uncomment java command for kotlin-client petstore scripts --- bin/kotlin-client-petstore.sh | 2 +- bin/windows/kotlin-client-petstore.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/kotlin-client-petstore.sh b/bin/kotlin-client-petstore.sh index 15163fa4531..d3eba9a2ae7 100755 --- a/bin/kotlin-client-petstore.sh +++ b/bin/kotlin-client-petstore.sh @@ -28,4 +28,4 @@ fi export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" ags="generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l kotlin --artifact-id kotlin-petstore-client -o samples/client/petstore/kotlin $@" -# java ${JAVA_OPTS} -jar ${executable} ${ags} +java ${JAVA_OPTS} -jar ${executable} ${ags} diff --git a/bin/windows/kotlin-client-petstore.bat b/bin/windows/kotlin-client-petstore.bat index c321501eda6..e9d8efffdaf 100644 --- a/bin/windows/kotlin-client-petstore.bat +++ b/bin/windows/kotlin-client-petstore.bat @@ -7,4 +7,4 @@ If Not Exist %executable% ( REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties set ags=generate --artifact-id "kotlin-petstore-client" -i modules\swagger-codegen\src\test\resources\2_0\petstore.yaml -l kotlin -o samples\client\petstore\kotlin -REM java %JAVA_OPTS% -jar %executable% %ags% +java %JAVA_OPTS% -jar %executable% %ags% From 9aa7e0b78a22908ba1c415748d6a7cf603608044 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 5 Mar 2018 17:18:22 +1100 Subject: [PATCH 9/9] Update for name change of kotlin-client generator --- bin/kotlin-client-petstore.sh | 2 +- bin/windows/kotlin-client-petstore.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/kotlin-client-petstore.sh b/bin/kotlin-client-petstore.sh index d3eba9a2ae7..f7e44d371e0 100755 --- a/bin/kotlin-client-petstore.sh +++ b/bin/kotlin-client-petstore.sh @@ -26,6 +26,6 @@ fi # if you've executed sbt assembly previously it will use that instead. export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" -ags="generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l kotlin --artifact-id kotlin-petstore-client -o samples/client/petstore/kotlin $@" +ags="generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l kotlin-client --artifact-id kotlin-petstore-client -o samples/client/petstore/kotlin $@" java ${JAVA_OPTS} -jar ${executable} ${ags} diff --git a/bin/windows/kotlin-client-petstore.bat b/bin/windows/kotlin-client-petstore.bat index e9d8efffdaf..9ad8b76efa1 100644 --- a/bin/windows/kotlin-client-petstore.bat +++ b/bin/windows/kotlin-client-petstore.bat @@ -5,6 +5,6 @@ If Not Exist %executable% ( ) REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties -set ags=generate --artifact-id "kotlin-petstore-client" -i modules\swagger-codegen\src\test\resources\2_0\petstore.yaml -l kotlin -o samples\client\petstore\kotlin +set ags=generate --artifact-id "kotlin-petstore-client" -i modules\swagger-codegen\src\test\resources\2_0\petstore.yaml -l kotlin-client -o samples\client\petstore\kotlin java %JAVA_OPTS% -jar %executable% %ags%