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

Open
#6,581 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.