Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/configs/kotlin-enum-default-value.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ additionalProperties:
serializableModel: "true"
dateLibrary: java8
enumUnknownDefaultCase: true
enumPropertyNaming: bestEffortBacktick
enumNameMappings:
CHRISTMAS_DAY: XMAS_DAY
2 changes: 1 addition & 1 deletion docs/generators/kotlin-misk.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |null|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|generateStubImplClasses|Generate Stub Impl Classes| |false|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/kotlin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |kotlin-server|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|featureAutoHead|Automatically provide responses to HEAD requests for existing routes that have the GET verb defined.| |true|
|featureCORS|Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org.| |false|
|featureCompression|Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.| |true|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|declarativeInterfaceReactiveMode|What type of reactive style to use in Spring Http declarative interface|<dl><dt>**coroutines**</dt><dd>Use kotlin-idiomatic 'suspend' functions</dd><dt>**reactor**</dt><dd>Use reactor return wrappers 'Mono' and 'Flux'</dd></dl>|coroutines|
|delegatePattern|Whether to generate the server files using the delegate pattern| |false|
|documentationProvider|Select the OpenAPI documentation provider.|<dl><dt>**none**</dt><dd>Do not publish an OpenAPI specification.</dd><dt>**source**</dt><dd>Publish the original input OpenAPI specification.</dd><dt>**springdoc**</dt><dd>Generate an OpenAPI 3 specification using SpringDoc.</dd></dl>|springdoc|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|exceptionHandler|generate default global exception handlers (not compatible with reactive. enabling reactive will disable exceptionHandler )| |true|
|generatePageableConstraintValidation|Generate a @ValidPageable annotation and PageableConstraintValidator class, and apply @ValidPageable to the injected Pageable parameter of operations whose 'page' or 'size' parameter specifies a maximum constraint. The annotation enforces those constraints on the Pageable object that replaces the individual page/size query parameters. Requires useBeanValidation=true and library=spring-boot.| |false|
|generateSortValidation|Generate a @ValidSort annotation and SortValidator class, and apply @ValidSort to the injected Pageable parameter of operations whose 'sort' parameter has enum values. The annotation validates that sort values in the Pageable object match the allowed enum values from the spec. Requires useBeanValidation=true and library=spring-boot.| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/kotlin-vertx.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |null|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
|modelMutable|Create mutable models| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/kotlin-wiremock.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |null|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
|modelMutable|Create mutable models| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|collectionType|Option. Collection type to use|<dl><dt>**array**</dt><dd>kotlin.Array</dd><dt>**list**</dt><dd>kotlin.collections.List</dd></dl>|list|
|companionObject|Whether to generate companion objects in data classes, enabling companion extensions.| |false|
|dateLibrary|Option. Date library to use|<dl><dt>**threetenbp-localdatetime**</dt><dd>Threetenbp - Backport of JSR310 (jvm only, for legacy app only)</dd><dt>**kotlinx-datetime**</dt><dd>kotlinx-datetime (preferred for multiplatform)</dd><dt>**string**</dt><dd>String</dd><dt>**java8-localdatetime**</dt><dd>Java 8 native JSR310 (jvm only, for legacy app only)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (jvm only, preferred for jdk 1.8+)</dd><dt>**threetenbp**</dt><dd>Threetenbp - Backport of JSR310 (jvm only, preferred for jdk &lt; 1.8)</dd></dl>|java8|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|explicitApi|Generates code with explicit access modifiers to comply with Kotlin Explicit API Mode.| |false|
|failOnUnknownProperties|Fail Jackson de-serialization on unknown properties| |false|
|generateOneOfAnyOfWrappers|Generate oneOf, anyOf schemas as wrappers. Only `jvm-retrofit2`(library) with `gson` or `kotlinx_serialization`(serializationLibrary) support this option.| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/ktorm-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|artifactId|Generated artifact id (name of jar).| |ktorm|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|defaultDatabaseName|Default database name for all queries| |sqlite.db|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|identifierNamingConvention|Naming convention of Ktorm identifiers(table names and column names). This is not related to database name which is defined by defaultDatabaseName option|<dl><dt>**original**</dt><dd>Do not transform original names</dd><dt>**snake_case**</dt><dd>Use snake_case names</dd></dl>|original|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,34 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co

@Setter protected boolean nonPublicApi = false;

@Getter protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.original;
/**
* Naming convention options for Kotlin enum properties. Extends the shared
* {@link org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE} with
* Kotlin-specific options so that Kotlin generators are not forced to add language-specific
* values to the shared enum (which cannot be extended in Java).
*/
public enum KotlinEnumNamingType {
camelCase, PascalCase, snake_case, original, UPPERCASE,
/**
* Like {@code original}, but uses Kotlin's backtick-escaped identifier syntax to preserve
* more values without falling back to sanitization. Where {@code original} would silently
* replace characters (e.g. {@code in-progress} → {@code inMinusProgress}), this option
* wraps the value in backticks instead (e.g. {@code `in-progress`}).
* <p>
* Particularly useful for sort/order enums whose values contain commas or other punctuation,
* e.g. {@code name,asc}, {@code name,desc}, {@code id,asc}, {@code id,desc} — these are
* preserved as `name,asc` etc. rather than being mangled into {@code nameCommaAsc}.
* </p>
* <ul>
* <li>Already a valid plain Kotlin identifier and not reserved → used as-is</li>
* <li>Contains no backtick / newline / CR / NUL → wrapped in backticks</li>
* <li>Otherwise → falls back to the standard sanitization (same as {@code original})</li>
* </ul>
*/
bestEffortBacktick
}

@Getter protected KotlinEnumNamingType enumPropertyNaming = KotlinEnumNamingType.original;

// model classes cannot use the same property names defined in HashMap
// ref: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-hash-map/
Expand Down Expand Up @@ -287,7 +314,11 @@ public AbstractKotlinCodegen() {
addOption(CodegenConstants.ARTIFACT_ID, "Generated artifact id (name of jar).", artifactId);
addOption(CodegenConstants.ARTIFACT_VERSION, "Generated artifact's package version.", artifactVersion);

CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC);
CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING,
"Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case'," +
" 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original'" +
" but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays" +
" `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)");
cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name()));

cliOptions.add(new CliOption(CodegenConstants.PARCELIZE_MODELS, CodegenConstants.PARCELIZE_MODELS_DESC));
Expand Down Expand Up @@ -341,14 +372,14 @@ public String escapeUnsafeCharacters(String input) {
/**
* Sets the naming convention for Kotlin enum properties
*
* @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE}
* @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link KotlinEnumNamingType}
*/
public void setEnumPropertyNaming(final String enumPropertyNamingType) {
try {
this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType);
this.enumPropertyNaming = KotlinEnumNamingType.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()) {
for (KotlinEnumNamingType t : KotlinEnumNamingType.values()) {
sb.append("\n ").append(t.name());
}
throw new RuntimeException(sb.toString());
Expand Down Expand Up @@ -682,6 +713,17 @@ public String toEnumVarName(String value, String datatype) {
case UPPERCASE:
modified = underscore(modified).toUpperCase(Locale.ROOT);
break;
case bestEffortBacktick:
// Use the original value as a plain identifier if already valid and not reserved.
if (!reservedWords.contains(value) && value.matches("[a-zA-Z_$][a-zA-Z0-9_$]*")) {
return value;
}
// Wrap in backticks when the value contains no character that is illegal inside them.
if (!value.contains("`") && !value.contains("\n") && !value.contains("\r") && !value.contains("\0")) {
return "`" + value + "`";
}
// Fall back: use the already-sanitized modified (pre-switch value).
break;
}

if (reservedWords.contains(modified)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
import static org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.*;
import static org.openapitools.codegen.languages.AbstractKotlinCodegen.KotlinEnumNamingType.bestEffortBacktick;
import static org.openapitools.codegen.TestUtils.createCodegenModelWrapper;
import static org.testng.Assert.*;

Expand Down Expand Up @@ -80,6 +82,81 @@ public void originalEnumConverter() {
assertEquals(codegen.toEnumVarName("data/*", null), "dataSlashStar");
}

@Test
public void bestEffortBacktickEnumConverter() {
codegen.setEnumPropertyNaming(bestEffortBacktick.name());

// Already a valid plain Kotlin identifier — no backticks needed
assertEquals(codegen.toEnumVarName("validName", null), "validName");
assertEquals(codegen.toEnumVarName("snake_case", null), "snake_case");

// Contains characters invalid in a plain identifier — wrap in backticks
assertEquals(codegen.toEnumVarName("long Name", null), "`long Name`");
assertEquals(codegen.toEnumVarName("long-Name", null), "`long-Name`");
assertEquals(codegen.toEnumVarName("not1long Name", null), "`not1long Name`");
assertEquals(codegen.toEnumVarName("data/*", null), "`data/*`");

// Starts with a digit — not a valid plain identifier, wrap in backticks
assertEquals(codegen.toEnumVarName("1long Name", null), "`1long Name`");

// Kotlin reserved word — wrap in backticks to make it valid
assertEquals(codegen.toEnumVarName("fun", null), "`fun`");
assertEquals(codegen.toEnumVarName("class", null), "`class`");

// Emoji — Unicode Symbol category, not a letter, so invalid as plain identifier; valid inside backticks
assertEquals(codegen.toEnumVarName("🎉", null), "`🎉`");

// Dollar sign is valid in plain Kotlin identifiers — no backticks needed
assertEquals(codegen.toEnumVarName("$price", null), "$price");

// Contains a literal backtick — cannot use backtick escaping, fall back to sanitization
assertEquals(codegen.toEnumVarName("foo`bar", null), "fooBacktickBar");
}

@Test(description = "bestEffortBacktick preserves original values as backtick identifiers in ComplexEnum")
public void testComplexEnumFromSpecWithBestEffortBacktick() {
codegen.setEnumPropertyNaming(bestEffortBacktick.name());
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/kotlin/issue10591-enum-defaultValue.yaml");
codegen.setOpenAPI(openAPI);

Schema complexEnumSchema = openAPI.getComponents().getSchemas().get("ComplexEnum");
CodegenModel cm = codegen.fromModel("ComplexEnum", complexEnumSchema);
codegen.postProcessModels(createCodegenModelWrapper(cm));

@SuppressWarnings("unchecked")
List<Map<String, Object>> enumVars = (List<Map<String, Object>>) cm.allowableValues.get("enumVars");
List<String> names = enumVars.stream()
.map(e -> (String) e.get("name"))
.collect(Collectors.toList());

// Already valid plain Kotlin identifiers — used as-is
assertTrue(names.contains("active"));
assertTrue(names.contains("inactive"));
assertTrue(names.contains("$yolo"));

// Contain a hyphen — wrapped in backticks
assertTrue(names.contains("`in-progress`"));
assertTrue(names.contains("`not-started`"));

// Sort/order enum values containing a comma — wrapped in backticks
assertTrue(names.contains("`name,asc`"));
assertTrue(names.contains("`name,desc`"));
assertTrue(names.contains("`id,asc`"));
assertTrue(names.contains("`id,desc`"));

// Contains a space — wrapped in backticks
assertTrue(names.contains("`not started`"));

// Kotlin reserved word — wrapped in backticks
assertTrue(names.contains("`class`"));

// Contains a literal backtick — cannot use backtick escaping, falls back to sanitization
assertTrue(names.contains("fooBacktickBar"));

// Contains emoji — wrapped in backticks
assertTrue(names.contains("`🎉`"));
}

@Test
public void pascalCaseEnumConverter() {
codegen.setEnumPropertyNaming(PascalCase.name());
Expand Down
Loading
Loading