diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java index 4dea75e8208..7a5d41c0dbe 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java @@ -146,6 +146,10 @@ public class CodegenConstants { public static final String DOTNET_FRAMEWORK_DESC = "The target .NET framework version."; public static enum MODEL_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, original} + public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, original, UPPERCASE} + + public static final String ENUM_PROPERTY_NAMING = "enumPropertyNaming"; + public static final String ENUM_PROPERTY_NAMING_DESC = "Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'"; public static final String MODEL_NAME_PREFIX = "modelNamePrefix"; public static final String MODEL_NAME_PREFIX_DESC = "Prefix that will be prepended to all model names. Default is the empty string."; 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 fbd9a7ebdb3..c8b4d009af4 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 @@ -23,6 +23,7 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig 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`. @@ -56,17 +57,26 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig )); // 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 select soft (contextual) keywords + // 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", @@ -74,20 +84,33 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig "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", @@ -96,6 +119,7 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig "typeof", "val", "var", + "vararg", "when", "while" )); @@ -149,12 +173,17 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig 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() { @@ -193,6 +222,10 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig 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 { @@ -255,6 +288,27 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig 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("/*", "/_*"); @@ -386,4 +440,69 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig 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; + + for (Map.Entry specialCharacters : specialCharReplacements.entrySet()) { + // Underscore is the only special character we'll allow + if (!specialCharacters.getKey().equals("_")) { + modified = modified.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue()); + } + } + + // Fallback, replace unknowns with underscore. + modified = modified.replaceAll("\\W+", "_"); + if (modified.matches("\\d.*")) { + modified = "_" + modified; + } + + // _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count. + if (modified.matches("^_*$")) { + modified = modified.replaceAll("\\Q_\\E", "Underscore"); + } + } + + 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 = result.substring(0, 1).toUpperCase() + result.substring(1); + break; + case snake_case: + // NOTE: Removes hyphens + modified = underscore(modified); + break; + case UPPERCASE: + modified = modified.toUpperCase(); + break; + } + + if (reservedWords.contains(modified)) { + // TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping). + return String.format("`%s`", modified); + } + + return modified; + } } diff --git a/modules/swagger-codegen/src/main/resources/kotlin-client/data_class.mustache b/modules/swagger-codegen/src/main/resources/kotlin-client/data_class.mustache index 4e83f8cda65..1237ec1f431 100644 --- a/modules/swagger-codegen/src/main/resources/kotlin-client/data_class.mustache +++ b/modules/swagger-codegen/src/main/resources/kotlin-client/data_class.mustache @@ -12,11 +12,14 @@ data class {{classname}} ( {{/-last}}{{/optionalVars}} ) { {{#hasEnums}}{{#vars}}{{#isEnum}} - enum class {{nameInCamelCase}}(val value: {{datatype}}) { - {{#_enum}} - {{{this}}}({{#isString}}"{{/isString}}{{{this}}}{{#isString}}"{{/isString}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} - {{/_enum}} + /** + * {{{description}}} + * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + */ + enum class {{nameInCamelCase}}(val value: {{dataType}}){ + {{#allowableValues}}{{#enumVars}} + {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{/enumVars}}{{/allowableValues}} } - {{/isEnum}}{{/vars}}{{/hasEnums}} -} \ No newline at end of file +} diff --git a/modules/swagger-codegen/src/main/resources/kotlin-client/enum_class.mustache b/modules/swagger-codegen/src/main/resources/kotlin-client/enum_class.mustache index 845fa192c21..791398b9789 100644 --- a/modules/swagger-codegen/src/main/resources/kotlin-client/enum_class.mustache +++ b/modules/swagger-codegen/src/main/resources/kotlin-client/enum_class.mustache @@ -1,9 +1,9 @@ /** * {{{description}}} -* Values: {{#allowableValues}}{{#enumVars}}{{name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} +* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} */ enum class {{classname}}(val value: {{dataType}}){ {{#allowableValues}}{{#enumVars}} - {{name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} {{/enumVars}}{{/allowableValues}} -} \ No newline at end of file +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenOptionsTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenOptionsTest.java index 55a4f8f06a8..c1d20f92217 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenOptionsTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenOptionsTest.java @@ -36,6 +36,8 @@ public class KotlinClientCodegenOptionsTest extends AbstractOptionsTest { times = 1; codegen.setSourceFolder(KotlinClientCodegenOptionsProvider.SOURCE_FOLDER); times = 1; + codegen.setEnumPropertyNaming(KotlinClientCodegenOptionsProvider.ENUM_PROPERTY_NAMING); + times = 1; }}; } } diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java index e27ab8a68c0..aeca5274aea 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java @@ -12,6 +12,7 @@ public class KotlinClientCodegenOptionsProvider implements OptionsProvider { public static final String ARTIFACT_ID = "swagger-kotlin-test"; public static final String GROUP_ID = "io.swagger.tests"; public static final String SOURCE_FOLDER = "./generated/kotlin"; + public static final String ENUM_PROPERTY_NAMING = "camelCase"; @Override public String getLanguage() { @@ -27,6 +28,7 @@ public class KotlinClientCodegenOptionsProvider implements OptionsProvider { .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(); }