OpenAPITools / OpenAPITools/openapi-generator
[BUG][JAVA][Feign][Spring] Client generated with library feign isn't compatible to Server generated by generator Spring when oneOf is in spec description
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator (example)?
- What's the version of OpenAPI Generator used?
- Have you search for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Bounty to sponsor the fix (example)
Description
Given YAML file with oneOf keyword (as described below), we generate a client with following configuration:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.build.directory}/maven-shared-archive-resources/public/openapi.yaml
</inputSpec>
<generatorName>java</generatorName>
<library>feign</library>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<apiPackage>com.github.sparsick.openapi.feign.bug.client.api</apiPackage>
<modelPackage>com.github.sparsick.openapi.feign.bug.client.model</modelPackage>
</configuration>
</execution>
</executions>
</plugin>
and we generate a server with this configuration:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.build.directory}/maven-shared-archive-resources/public/openapi.yaml</inputSpec>
<generatorName>spring</generatorName>
<supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
<apiPackage>com.github.sparsick.openapi.feign.bug.server.api</apiPackage>
<modelPackage>com.github.sparsick.openapi.feign.bug.server.model</modelPackage>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<skipDefaultInterface>true</skipDefaultInterface>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
When using the client to call a REST endpoint of this server, we don't get the expected data.
We discovered that for the feign client different model classes are generated as for the server.
An example:
For feign:
// ##### class Pet #######
@JsonPropertyOrder({
Pet.JSON_PROPERTY_ID,
Pet.JSON_PROPERTY_NAME,
Pet.JSON_PROPERTY_TAG,
Pet.JSON_PROPERTY_KIND_OF
})
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-06-08T09:25:55.463972826+02:00[Europe/Berlin]")
public class Pet {
public static final String JSON_PROPERTY_KIND_OF = "kindOf";
private PetKindOfOneOf kindOf;
}
// ###### interface PetKindOfOneOf ######
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-06-08T09:25:55.463972826+02:00[Europe/Berlin]")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "", visible = true)
@JsonSubTypes({
})
public interface PetKindOfOneOf {
}
// ###### class Fish #######
@JsonPropertyOrder({
Fish.JSON_PROPERTY_LOCATION
})
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-06-08T09:25:55.463972826+02:00[Europe/Berlin]")
public class Fish implements PetKindOfOneOf {
public static final String JSON_PROPERTY_LOCATION = "location";
private String location;
public Fish location(String location) {
this.location = location;
return this;
}
}
For server:
//##### class Pet ######
/**
* Pet
*/
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2020-06-08T09:25:58.274158782+02:00[Europe/Berlin]")
public class Pet {
@JsonProperty("kindOf")
private OneOfFishMammal kindOf;
}
//####### class Fish######
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2020-06-08T09:25:58.274158782+02:00[Europe/Berlin]")
public class Fish {
@JsonProperty("location")
private String location;
}
and we have to implement the class OneOfFishMammal ourselves. We choose following implementation:
public class OneOfFishMammal {
private Fish fish;
private Mammal mammal;
public Fish getFish() {
return fish;
}
public void setFish(Fish fish) {
this.fish = fish;
}
public Mammal getMammal() {
return mammal;
}
public void setMammal(Mammal mammal) {
this.mammal = mammal;
}
}
To demonstrate the problem we write following test:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PetControllerTest {
@LocalServerPort
private int port;
@Test
void test(){
ApiClient apiClient = new ApiClient();
PetsApi petsApi = apiClient.setBasePath("http://localhost:" + port).buildClient(PetsApi.class);
Pet pet = petsApi.petGet();
assertThat(pet.getKindOf()).isNotNull().isInstanceOf(Fish.class).extracting("location").isEqualTo("water");
}
}
// #### Controller implementation ####
@RestController
public class PetController implements PetApi {
@Override
public ResponseEntity<Pet> petGet() {
Pet pet = new Pet();
pet.setId(1L);
pet.setName("a pet");
OneOfFishMammal kindOf = new OneOfFishMammal();
Fish fish = new Fish();
fish.setLocation("water");
kindOf.setFish(fish);
pet.setKindOf(kindOf);
return new ResponseEntity<>(pet,HttpStatus.OK);
}
}
This test fails with
java.lang.AssertionError:
Expecting actual not to be null
at com.github.sparsick.openapi.feign.bug.server.PetControllerTest.test(PetControllerTest.java:28)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
You can find the whole sample here that demonstrates the problem completely.
openapi-generator version
openapi-maven-plugin in version 4.3.1
OpenAPI declaration file content or url
openapi: "3.0.0"
info:
version: 1.0.0
title: Example
license:
name: MIT
servers:
- url: /
paths:
/pet:
get:
tags:
- pets
responses:
'200':
description: 'A description'
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
kindOf:
type: object
oneOf:
- $ref: '#/components/schemas/Fish'
- $ref: '#/components/schemas/Mammal'
Fish:
type: object
properties:
location:
type: string
Mammal:
type: object
properties:
location:
type: string
Command line used for generation
using maven plugin for generation
Steps to reproduce
see PetControllerTest in the sample project
Related issues/PRs
no
Suggest a fix
Generator with feign should generate the same model like the other libs or the other way around.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked sample project and its PetControllerTest, then compare the Feign client and Spring server models generated by the Maven plugin from the provided OpenAPI declaration. Reproduce the failing kindOf assertion and make the generated models compatible so the Feign client deserializes the server's Fish response correctly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100