Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 8 additions & 0 deletions bin/configs/kotlin-multiplatform-oneOf-discriminator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-multiplatform-oneOf-discriminator
library: multiplatform
inputSpec: modules/openapi-generator/src/test/resources/3_0/kotlin/polymorphism-oneof-discriminator-simple.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
artifactId: kotlin-multiplatform-oneOf-discriminator
dateLibrary: kotlinx-datetime
Original file line number Diff line number Diff line change
Expand Up @@ -997,10 +997,53 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
continue;
}

boolean isOneOfModel = cm.oneOf != null && !cm.oneOf.isEmpty();

// For multiplatform oneOf with discriminator, generate sealed class polymorphism
// instead of brute-force wrapper deserialization
if (isOneOfModel && getLibrary() != null && getLibrary().equals(MULTIPLATFORM)) {
// Remove discriminator property from the parent - kotlinx.serialization handles it via @JsonClassDiscriminator
getAllVarProperties(cm).forEach(list -> list.removeIf(var -> var.name.equals(discriminator.getPropertyName())));

// Clear all merged properties from oneOf parent - they belong to children only
cm.vars.clear();
cm.allVars.clear();
cm.requiredVars.clear();
cm.optionalVars.clear();
cm.setHasVars(false);

// Mark this model to use sealed class rendering in the oneOf template
cm.vendorExtensions.put("x-oneof-sealed-class", true);

for (CodegenDiscriminator.MappedModel mappedModel : discriminator.getMappedModels()) {
CodegenModel childModel = mappedModel.getModel();

// Set parent-child relationship
childModel.setParent(cm.getClassname());
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
childModel.setParentModel(cm);

// Set discriminator value for @SerialName annotation
CodegenProperty additionalProperties = childModel.getAdditionalProperties();
if (additionalProperties == null) {
additionalProperties = new CodegenProperty();
childModel.setAdditionalProperties(additionalProperties);
}
additionalProperties.discriminatorValue = mappedModel.getMappingName();

// Remove discriminator property from child - handled by kotlinx.serialization
getAllVarProperties(childModel).forEach(list -> list.removeIf(prop -> prop.name.equals(discriminator.getPropertyName())));

if (childModel.vars.isEmpty() && !childModel.isEnum && !childModel.isAlias) {
childModel.setHasVars(false);
}
}
continue;
}

// When using generateOneOfAnyOfWrappers and encountering oneOf, we keep discriminator properties,
// because single entity can be referenced in multiple "parent" entities,
// so discriminator for one might not be discriminator for another.
boolean shouldKeepDiscriminatorField = generateOneOfAnyOfWrappers && cm.oneOf != null && !cm.oneOf.isEmpty();
boolean shouldKeepDiscriminatorField = generateOneOfAnyOfWrappers && isOneOfModel;

if (shouldKeepDiscriminatorField) {
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
{{#multiplatform}}
{{#vendorExtensions.x-oneof-sealed-class}}
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.JsonClassDiscriminator

/**
* {{{description}}}
*
*/
{{#isDeprecated}}
@Deprecated(message = "This schema is deprecated.")
{{/isDeprecated}}
@Serializable
@OptIn(ExperimentalSerializationApi::class)
@JsonClassDiscriminator(discriminator = "{{{discriminator.propertyName}}}")
{{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}sealed class {{classname}}
{{/vendorExtensions.x-oneof-sealed-class}}
{{^vendorExtensions.x-oneof-sealed-class}}
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
Expand Down Expand Up @@ -76,4 +95,5 @@ import kotlinx.serialization.json.*
}
}
}
{{/multiplatform}}
{{/vendorExtensions.x-oneof-sealed-class}}
{{/multiplatform}}
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,55 @@ public void testCompanionObjectGeneratesCompanionInModel() throws IOException {
TestUtils.assertFileContains(petModel, "companion object { }");
}

@Test(description = "generate multiplatform oneOf with discriminator as sealed class")
public void multiplatformOneOfDiscriminatorSealedClass() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("kotlin")
.setLibrary("multiplatform")
.setAdditionalProperties(new HashMap<>() {{
put("dateLibrary", "kotlinx-datetime");
}})
.setInputSpec("src/test/resources/3_0/kotlin/polymorphism-oneof-discriminator-simple.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));

final ClientOptInput clientOptInput = configurator.toClientOptInput();
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(clientOptInput).generate();

final Path petKt = Paths.get(output + "/src/commonMain/kotlin/org/openapitools/client/models/Pet.kt");
// parent is sealed class with discriminator annotation
TestUtils.assertFileContains(petKt, "sealed class Pet");
TestUtils.assertFileContains(petKt, "@JsonClassDiscriminator(discriminator = \"petType\")");
TestUtils.assertFileContains(petKt, "@Serializable");
// parent does not contain discriminator property or child properties
TestUtils.assertFileNotContains(petKt, "val petType");
TestUtils.assertFileNotContains(petKt, "val breed");
TestUtils.assertFileNotContains(petKt, "val color");

final Path dogKt = Paths.get(output + "/src/commonMain/kotlin/org/openapitools/client/models/Dog.kt");
// child extends parent
TestUtils.assertFileContains(dogKt, "data class Dog");
TestUtils.assertFileContains(dogKt, ": Pet()");
// child has discriminator value annotation
TestUtils.assertFileContains(dogKt, "@SerialName(value = \"DOG\")");
// child does not contain discriminator property
TestUtils.assertFileNotContains(dogKt, "val petType");
// child has its own properties
TestUtils.assertFileContains(dogKt, "val breed");

final Path catKt = Paths.get(output + "/src/commonMain/kotlin/org/openapitools/client/models/Cat.kt");
// child extends parent
TestUtils.assertFileContains(catKt, "data class Cat");
TestUtils.assertFileContains(catKt, ": Pet()");
// child has discriminator value annotation
TestUtils.assertFileContains(catKt, "@SerialName(value = \"CAT\")");
// child has its own properties
TestUtils.assertFileContains(catKt, "val color");
}

private static class ModelNameTest {
private final String expectedName;
private final String expectedClassName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
openapi: 3.0.1
info:
title: Example
description: OneOf with discriminator example
version: '0.1'
contact:
email: contact@example.org
url: 'https://example.org'
servers:
- url: http://example.org
tags:
- name: pet
paths:
'/v1/pet/{id}':
get:
tags:
- pet
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/pet'
operationId: get-pet
parameters:
- schema:
type: string
format: uuid
name: id
in: path
required: true
components:
schemas:
pet:
title: A pet
oneOf:
- $ref: '#/components/schemas/dog'
- $ref: '#/components/schemas/cat'
discriminator:
propertyName: petType
mapping:
DOG: '#/components/schemas/dog'
CAT: '#/components/schemas/cat'
dog:
title: A dog
required:
- petType
- breed
properties:
petType:
type: string
breed:
type: string
bark:
type: boolean
cat:
title: A cat
required:
- petType
- color
properties:
petType:
type: string
color:
type: string
indoor:
type: boolean
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.openapi-generator-ignore
README.md
build.gradle.kts
docs/Cat.md
docs/Dog.md
docs/Pet.md
docs/PetApi.md
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
settings.gradle.kts
src/commonMain/kotlin/org/openapitools/client/apis/PetApi.kt
src/commonMain/kotlin/org/openapitools/client/auth/ApiKeyAuth.kt
src/commonMain/kotlin/org/openapitools/client/auth/Authentication.kt
src/commonMain/kotlin/org/openapitools/client/auth/HttpBasicAuth.kt
src/commonMain/kotlin/org/openapitools/client/auth/HttpBearerAuth.kt
src/commonMain/kotlin/org/openapitools/client/auth/OAuth.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/Base64ByteArray.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/HttpResponse.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/OctetByteArray.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/PartConfig.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt
src/commonMain/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt
src/commonMain/kotlin/org/openapitools/client/models/Cat.kt
src/commonMain/kotlin/org/openapitools/client/models/Dog.kt
src/commonMain/kotlin/org/openapitools/client/models/Pet.kt
src/test/kotlin/org/openapitools/client/apis/PetApiTest.kt
src/test/kotlin/org/openapitools/client/models/CatTest.kt
src/test/kotlin/org/openapitools/client/models/DogTest.kt
src/test/kotlin/org/openapitools/client/models/PetTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.21.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# org.openapitools.client - Kotlin client library for Example

OneOf with discriminator example

## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate an API client.

- API version: 0.1
- Package version:
- Generator version: 7.21.0-SNAPSHOT
- Build package: org.openapitools.codegen.languages.KotlinClientCodegen
For more information, please visit [https://example.org](https://example.org)

## Requires

* Kotlin 2.2.20

## Build

```
./gradlew check assemble
```

This runs all tests and packages the library.

## Features/Implementation Notes

* Supports JSON inputs/outputs, File inputs, and Form inputs.
* Supports collection formats for query parameters: csv, tsv, ssv, pipes.
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.


<a id="documentation-for-api-endpoints"></a>
## Documentation for API Endpoints

All URIs are relative to *http://example.org*

| Class | Method | HTTP request | Description |
| ------------ | ------------- | ------------- | ------------- |
| *PetApi* | [**getPet**](docs/PetApi.md#getpet) | **GET** /v1/pet/{id} | |


<a id="documentation-for-models"></a>
## Documentation for Models

- [org.openapitools.client.models.Cat](docs/Cat.md)
- [org.openapitools.client.models.Dog](docs/Dog.md)
- [org.openapitools.client.models.Pet](docs/Pet.md)


<a id="documentation-for-authorization"></a>
## Documentation for Authorization

Endpoints do not require authorization.



## Author

contact@example.org
Loading