Skip to content

Commit

Permalink
[BUG] Issue 10792 Kotlin generator produces invalid code when allOf i…
Browse files Browse the repository at this point in the history
…s used (#12594)

* Step to reproduces

* Fix isMap detection for kotlin codegen

Co-authored-by: Eric Durand-Tremblay <etremblay@kronostechnologies.com>
  • Loading branch information
etremblay and Eric Durand-Tremblay authored Jun 22, 2022
1 parent 75b883c commit c38d825
Show file tree
Hide file tree
Showing 40 changed files with 1,435 additions and 0 deletions.
9 changes: 9 additions & 0 deletions bin/configs/kotlin-allOff-discriminator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-allOff-discriminator
inputSpec: modules/openapi-generator/src/test/resources/3_0/issue_10792.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
artifactId: kotlin-allOff-discriminator
serializableModel: "false"
dateLibrary: java8
enumUnknownDefaultCase: true
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.fasterxml.jackson.databind.node.ArrayNode;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import org.apache.commons.io.FilenameUtils;
Expand Down Expand Up @@ -1045,4 +1046,30 @@ public String toDefaultValue(Schema schema) {
public GeneratorLanguage generatorLanguage() {
return GeneratorLanguage.KOTLIN;
}

@Override
protected void updateModelForObject(CodegenModel m, Schema schema) {
/**
* we have a custom version of this function so we only set isMap to true if
* ModelUtils.isMapSchema
* In other generators, isMap is true for all type object schemas
*/
if (schema.getProperties() != null || schema.getRequired() != null && !(schema instanceof ComposedSchema)) {
// passing null to allProperties and allRequired as there's no parent
addVars(m, unaliasPropertySchema(schema.getProperties()), schema.getRequired(), null, null);
}
if (ModelUtils.isMapSchema(schema)) {
// an object or anyType composed schema that has additionalProperties set
addAdditionPropertiesToCodeGenModel(m, schema);
} else {
m.setIsMap(false);
if (ModelUtils.isFreeFormObject(openAPI, schema)) {
// non-composed object type with no properties + additionalProperties
// additionalProperties must be null, ObjectSchema, or empty Schema
addAdditionPropertiesToCodeGenModel(m, schema);
}
}
// process 'additionalProperties'
setAddProps(schema, m);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,44 @@ public void testEnumPropertyWithDefaultValue() {
Assert.assertEquals(cp1.getEnumName(), "PropertyName");
Assert.assertEquals(cp1.getDefaultValue(), "PropertyName.vALUE");
}

@Test(description = "Issue #10792")
public void handleInheritanceWithObjectTypeShouldNotBeAMap() {
Schema parent = new ObjectSchema()
.addProperties("a", new StringSchema())
.addProperties("b", new StringSchema())
.addRequiredItem("a")
.name("Parent");
Schema child = new ComposedSchema()
.addAllOfItem(new Schema().$ref("Parent"))
.addAllOfItem(new ObjectSchema()
.addProperties("c", new StringSchema())
.addProperties("d", new StringSchema())
.addRequiredItem("c"))
.name("Child")
.type("object"); // Without the object type it is not wrongly recognized as map
Schema mapSchema = new ObjectSchema()
.addProperties("a", new StringSchema())
.additionalProperties(Boolean.TRUE)
.name("MapSchema")
.type("object");

OpenAPI openAPI = TestUtils.createOpenAPI();
openAPI.getComponents().addSchemas(parent.getName(), parent);
openAPI.getComponents().addSchemas(child.getName(), child);
openAPI.getComponents().addSchemas(mapSchema.getName(), mapSchema);

final DefaultCodegen codegen = new P_AbstractKotlinCodegen();
codegen.setOpenAPI(openAPI);

final CodegenModel pm = codegen
.fromModel("Child", child);

Assert.assertFalse(pm.isMap);

// Make sure a real map is still flagged as map
final CodegenModel mapSchemaModel = codegen
.fromModel("MapSchema", mapSchema);
Assert.assertTrue(mapSchemaModel.isMap);
}
}
57 changes: 57 additions & 0 deletions modules/openapi-generator/src/test/resources/3_0/issue_10792.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
openapi: 3.0.1
info:
title: Example
description: An example
version: '0.1'
contact:
email: contact@example.org
url: 'https://example.org'
servers:
- url: http://example.org
tags:
- name: bird
paths:
'/v1/bird/{id}':
get:
tags:
- bird
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/bird'
operationId: get-bird
parameters:
- schema:
type: string
format: uuid
name: id
in: path
required: true
components:
schemas:
animal:
title: An animal
type: object
properties:
id:
type: string
format: uuid
required:
- id
discriminator:
propertyName: type
mapping:
BIRD: '#/components/schemas/bird'
bird:
title: A bird
type: object
allOf:
- $ref: '#/components/schemas/animal'
- properties:
featherType:
type: string
required:
- featherType
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 @@
README.md
build.gradle
docs/Animal.md
docs/Bird.md
docs/BirdAllOf.md
docs/BirdApi.md
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
settings.gradle
src/main/kotlin/org/openapitools/client/apis/BirdApi.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiResponse.kt
src/main/kotlin/org/openapitools/client/infrastructure/BigDecimalAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/BigIntegerAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/Errors.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt
src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt
src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt
src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
src/main/kotlin/org/openapitools/client/infrastructure/SerializerHelper.kt
src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt
src/main/kotlin/org/openapitools/client/models/Animal.kt
src/main/kotlin/org/openapitools/client/models/Bird.kt
src/main/kotlin/org/openapitools/client/models/BirdAllOf.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.0.1-SNAPSHOT
52 changes: 52 additions & 0 deletions samples/client/petstore/kotlin-allOff-discriminator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# org.openapitools.client - Kotlin client library for Example

## Requires

* Kotlin 1.4.30
* Gradle 6.8.3

## Build

First, create the gradle wrapper script:

```
gradle wrapper
```

Then, run:

```
./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.
* Implementation of ApiClient is intended to reduce method counts, specifically to benefit Android targets.

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

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

Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*BirdApi* | [**getBird**](docs/BirdApi.md#getbird) | **GET** /v1/bird/{id} |


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

- [org.openapitools.client.models.Animal](docs/Animal.md)
- [org.openapitools.client.models.Bird](docs/Bird.md)
- [org.openapitools.client.models.BirdAllOf](docs/BirdAllOf.md)


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

All endpoints do not require authorization.
37 changes: 37 additions & 0 deletions samples/client/petstore/kotlin-allOff-discriminator/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
group 'org.openapitools'
version '1.0.0'

wrapper {
gradleVersion = '6.8.3'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}

buildscript {
ext.kotlin_version = '1.5.10'

repositories {
maven { url "https://repo1.maven.org/maven2" }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

apply plugin: 'kotlin'

repositories {
maven { url "https://repo1.maven.org/maven2" }
}

test {
useJUnitPlatform()
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
implementation "com.squareup.moshi:moshi-adapters:1.12.0"
implementation "com.squareup.okhttp3:okhttp:4.9.1"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
}
10 changes: 10 additions & 0 deletions samples/client/petstore/kotlin-allOff-discriminator/docs/Animal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

# Animal

## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | [**java.util.UUID**](java.util.UUID.md) | |



10 changes: 10 additions & 0 deletions samples/client/petstore/kotlin-allOff-discriminator/docs/Bird.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

# Bird

## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**featherType** | **kotlin.String** | |



Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

# BirdAllOf

## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**featherType** | **kotlin.String** | |



Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# BirdApi

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

Method | HTTP request | Description
------------- | ------------- | -------------
[**getBird**](BirdApi.md#getBird) | **GET** /v1/bird/{id} |


<a name="getBird"></a>
# **getBird**
> Bird getBird(id)


### Example
```kotlin
// Import classes:
//import org.openapitools.client.infrastructure.*
//import org.openapitools.client.models.*

val apiInstance = BirdApi()
val id : java.util.UUID = 38400000-8cf0-11bd-b23e-10b96e4ef00d // java.util.UUID |
try {
val result : Bird = apiInstance.getBird(id)
println(result)
} catch (e: ClientException) {
println("4xx response calling BirdApi#getBird")
e.printStackTrace()
} catch (e: ServerException) {
println("5xx response calling BirdApi#getBird")
e.printStackTrace()
}
```

### Parameters

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **java.util.UUID**| |

### Return type

[**Bird**](Bird.md)

### Authorization

No authorization required

### HTTP request headers

- **Content-Type**: Not defined
- **Accept**: application/json

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading

0 comments on commit c38d825

Please sign in to comment.