Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d053a5b
update docs for generators
dijkstrar Jun 3, 2026
b7a8641
only apply flag to kotlin spring generator
dijkstrar Jun 3, 2026
cf86eb0
add unknown default value to moustache, and make tests to assert corr…
dijkstrar Jun 3, 2026
c9a6337
address review comments
dijkstrar Jun 3, 2026
586b171
fix typo in cliOption to CliOption...
dijkstrar Jun 4, 2026
b899d9d
oneline the override in the moustache file
dijkstrar Jun 4, 2026
84be4bf
add unknown default enum case to the dataclass moustache file
dijkstrar Jun 4, 2026
599444e
test the inclusion of the enum default for the dataclass files
dijkstrar Jun 4, 2026
e7702b2
create resources file for testing enum dataclass default
dijkstrar Jun 4, 2026
df45fd0
fallback is non nullable, resolves to 11184809
dijkstrar Jun 4, 2026
5f444ca
update kotlin spring docs so that enumUnknownDefaultCase docs is equa…
dijkstrar Jun 5, 2026
cabc59f
alter test assertions to only assert on UNKNOWN_DEFAULT_OPEN_API and …
dijkstrar Jun 7, 2026
a3bd32d
enum is correctly generated, but one of the cases uses incorrect capi…
dijkstrar Jun 9, 2026
8b2a43b
alter whitespace in enum class moustache, and generate dataclass mous…
dijkstrar Jun 9, 2026
cf4f29c
alter white space in moustache files, remove useless it->it.value==...
dijkstrar Jun 9, 2026
270695d
also make testcase for enum integers in kotlin spring :)
dijkstrar Jun 9, 2026
1cedbe5
tests now assert based on correctly, originally, capitalized unknown_…
dijkstrar Jun 9, 2026
929309e
test based on the lowercased value of unknown_default_open_api, and a…
dijkstrar Jun 10, 2026
99e545d
dont test on the enum vars themselves, but rely on the ci workflow fo…
dijkstrar Jun 10, 2026
316833e
update docs based on provided script
dijkstrar Jun 10, 2026
9e3518d
Merge branch 'master' into feature/add-kotlin-enumUnknownDefaultCase-…
dijkstrar Jun 26, 2026
dece211
add extra line after main merge...
dijkstrar Jun 26, 2026
532bb02
fix compilation error from errornous merge in gh webui
dijkstrar Jul 31, 2026
1916921
fix whitespace formatting after review
dijkstrar Jul 31, 2026
1b280d3
alter whitespaces, and include the altered generated sample files
dijkstrar Jul 31, 2026
7f1cf1c
alter whitespaces in moustache files, and also in generated petstore …
dijkstrar Jul 31, 2026
649b2a2
alter whitespaces in enumClass moustache file, to remove empty newlines
dijkstrar Jul 31, 2026
4eff29f
alter whitespaces in dataclass moustache file, to remove weird tabs a…
dijkstrar Jul 31, 2026
f7f44c9
add newline after enum class definition, and before enum listings!
dijkstrar Jul 31, 2026
0133dc3
fix tab/space formatting issues
dijkstrar Jul 31, 2026
5e21baa
final tab alignments..
dijkstrar Jul 31, 2026
2488e44
bump openapi version to 3_0_1
dijkstrar Sep 1, 2026
214b0c7
Merge remote-tracking branch 'upstream/master' into feature/add-kotli…
dijkstrar Sep 4, 2026
7ae634a
generate samples, after merging main and some other hassles..
dijkstrar Sep 4, 2026
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 docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|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', '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|
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.| |false|
|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
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ public KotlinSpringServerCodegen() {
addSwitch(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", companionObject);
addSwitch(SUSPEND_FUNCTIONS, "Whether to generate suspend functions for API operations. Useful for Spring MVC with Kotlin coroutines without requiring the full reactive stack.", suspendFunctions);
cliOptions.add(CliOption.newBoolean(CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES, CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES_DESC, useDeductionForOneOfInterfaces));
cliOptions.add(cliOption.newBoolean(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE_DESC).defaultValue("false"));

addSwitch(CodegenConstants.USE_ENUM_VALUE_INTERFACE, CodegenConstants.USE_ENUM_VALUE_INTERFACE_DESC, useEnumValueInterface);
addSwitch(CodegenConstants.OPENAPI_NULLABLE,
"Enable OpenAPI Jackson Nullable library (jackson-databind-nullable) for optional + nullable "
Expand Down Expand Up @@ -659,6 +661,11 @@ public void processOpts() {
}
writePropertyBack(SKIP_DEFAULT_INTERFACE, skipDefaultInterface);


if (additionalProperties.containsKey(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE)) {
setEnumUnknownDefaultCase(Boolean.parseBoolean(additionalProperties.get(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE).toString()));
}

if (additionalProperties.containsKey(REACTIVE)) {
if (SPRING_CLOUD_LIBRARY.equals(library)) {
throw new IllegalArgumentException("Currently, reactive option doesn't supported by Spring Cloud");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
/**
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{classname}}(@get:JsonValue {{#useEnumValueInterface}}override {{/useEnumValueInterface}}val value: {{dataType}}) {{#vendorExtensions.x-kotlin-implements}}{{#-first}}: {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}} {{/vendorExtensions.x-kotlin-implements}}{
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{classname}}(
@get:JsonValue
{{#useEnumValueInterface}}override {{/useEnumValueInterface}}
val value: {{dataType}}
Comment thread
dijkstrar marked this conversation as resolved.
Outdated
) {{#vendorExtensions.x-kotlin-implements}}{{#-first}}: {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-kotlin-implements}} {

{{#allowableValues}}{{#enumVars}}
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}};
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
{{#enumUnknownDefaultCase}},
UNKNOWN_DEFAULT_OPEN_API({{#isString}}"unknown_default_open_api"{{/isString}}{{^isString}}{{#dataType}}null{{/dataType}}{{/isString}})
{{/enumUnknownDefaultCase}};

companion object {
@JvmStatic
@JsonCreator
fun forValue(value: {{dataType}}): {{classname}} {
return values().firstOrNull{it -> it.value == value}
?: throw IllegalArgumentException("Unexpected value '$value' for enum '{{classname}}'")
return values().firstOrNull { it.value == value }
{{#enumUnknownDefaultCase}}
?: UNKNOWN_DEFAULT_OPEN_API
{{/enumUnknownDefaultCase}}
{{^enumUnknownDefaultCase}}
?: throw IllegalArgumentException(
"Unexpected value '$value' for enum '{{classname}}'"
)
{{/enumUnknownDefaultCase}}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6678,4 +6678,116 @@ public void schemaMappingWithNullableAllOfRendersNullableKotlinProperty() throws
String content = Files.readString(myObjectFile.toPath());
assertThat(content).contains("com.example.ExternalModel?");
}

@Test(description = "test enumUnknownDefaultCase option")
public void testEnumUnknownDefaultCaseOption() {
final KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();

// Test default value is false
codegen.processOpts();
Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.FALSE);

// Test setting via additionalProperties
codegen.additionalProperties().put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
codegen.processOpts();
Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.TRUE);
}

@Test(description = "test enum model generation with enumUnknownDefaultCase")
public void testEnumModelWithUnknownDefaultCase() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_1/enum_unknown_default_case.yaml");
final KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();

// Enable enumUnknownDefaultCase
codegen.additionalProperties().put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
codegen.setOpenAPI(openAPI);
codegen.processOpts();

// Verify that enumUnknownDefaultCase is set
Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.TRUE);

// Process all models to trigger enum processing
Map<String, Schema> schemas = openAPI.getComponents().getSchemas();
Map<String, ModelsMap> allModels = new HashMap<>();
for (String modelName : schemas.keySet()) {
Schema schema = schemas.get(modelName);
CodegenModel cm = codegen.fromModel(modelName, schema);
ModelsMap modelsMap = new ModelsMap();
modelsMap.setModels(Collections.singletonList(new ModelMap(Collections.singletonMap("model", cm))));
allModels.put(modelName, modelsMap);
}

// Post-process to add enumVars
allModels = codegen.postProcessAllModels(allModels);

// Get the ColorEnum model
CodegenModel colorEnum = null;
for (Map.Entry<String, ModelsMap> entry : allModels.entrySet()) {
if ("ColorEnum".equals(entry.getKey())) {
colorEnum = entry.getValue().getModels().get(0).getModel();
break;
}
}

Assert.assertNotNull(colorEnum);
Assert.assertNotNull(colorEnum.allowableValues);

List<Map<String, Object>> enumVars = (List<Map<String, Object>>) colorEnum.allowableValues.get("enumVars");
Assert.assertNotNull(enumVars);

// Check that we have the expected enum values including UNKNOWN_DEFAULT_OPEN_API
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'RED'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'GREEN'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'BLUE'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'YELLOW'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'unknown_default_open_api'".equals(var.get("value"))));
}

@Test(description = "test enum generation with enumUnknownDefaultCase enabled")
public void testEnumGenerationWithUnknownDefaultCase() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("kotlin-spring")
.setInputSpec("src/test/resources/3_1/enum_unknown_default_case.yaml")
.setOutputDir(outputPath)
.addAdditionalProperty(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");

DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);

Path enumFile = Paths.get(outputPath, "openapi_client", "models", "color_enum.kt");
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

TestUtils.assertFileContains(enumFile,
"UNKNOWN_DEFAULT_OPEN_API");

TestUtils.assertFileContains(enumFile,
"?: UNKNOWN_DEFAULT_OPEN_API");
}

@Test(description = "test enum generation with enumUnknownDefaultCase disabled")
public void testEnumGenerationWithoutUnknownDefaultCase() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("kotlin-spring")
.setInputSpec("src/test/resources/3_1/enum_unknown_default_case.yaml")
.setOutputDir(outputPath)
.addAdditionalProperty(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "false");

DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);

Path enumFile = Paths.get(outputPath, "openapi_client", "models", "color_enum.kt");

// Check that UNKNOWN_DEFAULT_OPEN_API is NOT added
TestUtils.assertFileNotContains(enumFile, "UNKNOWN_DEFAULT_OPEN_API");

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
openapi: 3.0.0
info:
title: Enum Test API
description: API for testing enum generation with enumUnknownDefaultCase
version: 1.0.0
paths:
/colors:
get:
summary: Get color
operationId: getColor
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/ColorResponse'
components:
schemas:
ColorResponse:
type: object
required:
- color
- status
properties:
color:
$ref: '#/components/schemas/ColorEnum'
status:
$ref: '#/components/schemas/StatusEnum'
priority:
$ref: '#/components/schemas/PriorityEnum'
ColorEnum:
type: string
description: Available colors
enum:
- RED
- GREEN
- BLUE
- YELLOW
StatusEnum:
type: string
description: Status values
enum:
- PENDING
- APPROVED
- REJECTED
- IN_PROGRESS
PriorityEnum:
type: integer
description: Priority levels
enum:
- 1
- 2
- 3
- 4
- 5