OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.69k stars 6.55k forks source link

[BUG] Description #16091

Open quesojeany opened 1 year ago

quesojeany commented 1 year ago

Bug Report Checklist

Description

Using 7.0.0-beta of the openapi-generator-maven-plugin (library = spring-boot):

Enum Type as discriminator feature https://github.com/OpenAPITools/openapi-generator/pull/13846 (oneOf interface polymorphism) ignores modelNameSuffix specified in the plugin configurations when generating the abstract get{Discriminator}() method for the interface, but is considering it while generating the enum and concrete class' relevant discriminator field and set/get methods. This results in a compile time error (incompatible types).

Presume this is an issue for modelNamePrefix as well.

openapi-generator version

7.0.0-beta

OpenAPI declaration file content or url

Yaml:

    FruitType:
      enum:
      - APPLE
      - BANANA
      type: string
    Fruit:
      discriminator:
        mapping:
          APPLE: '#/components/schemas/Apple'
          BANANA: '#/components/schemas/Banana'
        propertyName: fruitType
      oneOf:
      - $ref: '#/components/schemas/Apple'
      - $ref: '#/components/schemas/Banana'
      properties:
        fruitType:
          $ref: '#/components/schemas/FruitType'
      required:
      - fruitType
      type: object
    Apple:
      properties:
        seeds:
          type: integer
      required:
      - seeds
      type: object
    Banana:
      properties:
        length:
          type: integer
      required:
      - length
      type: object
Generation Details

Maven plugin (the bits that matter):

<configuration>
    <generatorName>spring</generatorName>
    <library>spring-boot</library>
    <modelNameSuffix>Dto</modelNameSuffix>
    ....
</configuration>
Generated Discriminator Enum, Interface and Concrete classes

Enum:


@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
public enum FruitTypeDto {

  APPLE("APPLE"),

  BANANA("BANANA");

  private String value;

  FruitTypeDto(String value) {
    this.value = value;
  }

  @JsonValue
  public String getValue() {
    return value;
  }

  @Override
  public String toString() {
    return String.valueOf(value);
  }

  @JsonCreator
  public static FruitTypeDto fromValue(String value) {
    for (FruitTypeDto b : FruitTypeDto.values()) {
      if (b.value.equals(value)) {
        return b;
      }
    }
    throw new IllegalArgumentException("Unexpected value '" + value + "'");
  }
}

Interface:


@JsonIgnoreProperties(
  value = "fruitType", // ignore manually set fruitType, it will be automatically generated by Jackson during serialization
  allowSetters = true // allows the fruitType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "fruitType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = Apple.class, name = "APPLE"),
  @JsonSubTypes.Type(value = Apple.class, name = "Apple"),
  @JsonSubTypes.Type(value = Banana.class, name = "BANANA"),
  @JsonSubTypes.Type(value = Banana.class, name = "Banana")
})

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
public interface Fruit {
    public FruitType getFruitType();
}

Concrete class (Banana is generated the same considering the model suffix)


@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
public class Apple implements Fruit {

  private Integer seeds;

  private FruitTypeDto fruitType;

  public Apple() {
    super();
  }

  /**
   * Constructor with only required parameters
   */
  public Apple(Integer seeds) {
    this.seeds = seeds;
    this.fruitType = fruitType;
  }

  public Apple seeds(Integer seeds) {
    this.seeds = seeds;
    return this;
  }

  /**
   * Get seeds
   * @return seeds
  */
  @NotNull 
  @Schema(name = "seeds", requiredMode = Schema.RequiredMode.REQUIRED)
  @JsonProperty("seeds")
  public Integer getSeeds() {
    return seeds;
  }

  public void setSeeds(Integer seeds) {
    this.seeds = seeds;
  }

  public Apple fruitType(FruitTypeDto fruitType) {
    this.fruitType = fruitType;
    return this;
  }

  /**
   * Get fruitType
   * @return fruitType
  */
  @NotNull @Valid 
  @Schema(name = "fruitType", requiredMode = Schema.RequiredMode.REQUIRED)
  @JsonProperty("fruitType")
  public FruitTypeDto getFruitType() {
    return fruitType;
  }

  public void setFruitType(FruitTypeDto fruitType) {
    this.fruitType = fruitType;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Apple apple = (Apple) o;
    return Objects.equals(this.seeds, apple.seeds) &&
        Objects.equals(this.fruitType, apple.fruitType);
  }

  @Override
  public int hashCode() {
    return Objects.hash(seeds, fruitType);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class Apple {\n");
    sb.append("    seeds: ").append(toIndentedString(seeds)).append("\n");
    sb.append("    fruitType: ").append(toIndentedString(fruitType)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}
Steps to reproduce
Related issues/PRs

https://github.com/OpenAPITools/openapi-generator/pull/13846

Suggest a fix/workaround

current workarounds:

  1. if one wants to leverage oneOf which generates Interface and concrete classes: don't configure modelNameSuffix/modelNamePrefix
  2. if one wants to keep the modelNameSuffix/Prefix: Leverage composition instead--> Generate base concrete class and subclasses by removing oneOf and specify allOf in the child schemas. The resulting generated code retains the DTO in the base concrete model class.

sample yaml for option two workaround


    FruitType:
      enum:
        - APPLE
        - BANANA
      type: string
    Fruit:
      discriminator:
        mapping:
          APPLE: '#/components/schemas/Apple'
          BANANA: '#/components/schemas/Banana'
        propertyName: fruitType
      properties:
        fruitType:
          $ref: '#/components/schemas/FruitType'
      required:
        - fruitType
      type: object
    Apple:
      allOf:
        - $ref: '#/components/schemas/Fruit'
        - properties:
            seeds:
              type: integer
          required:
            - seeds
          type: object
    Banana:
      allOf:
        - $ref: '#/components/schemas/Fruit'
        - properties:
            length:
              type: integer
          required:
            - length
          type: object
  
aparoha commented 1 year ago

Thanks @quesojeany for reporting the bug. I am also facing the same issue. <modelNameSuffix>Dto</modelNameSuffix>stopped working after upgrading plugin version to "7.0.0-beta"