swagger-core icon indicating copy to clipboard operation
swagger-core copied to clipboard

Polymorphic arrays are not properly documented using Swagger Annotations

Open azagorneanu opened this issue 4 years ago • 1 comments

Given the following endpoint and response classes:

@Path("/test_polymorphic")
@Tag(name = "/test_polymorphic")
public interface PolymorphicEntityEndpoints {
    @GET
    @Path("/{id}")
    @Operation(summary = "Get the entity by id")
    EntityResponse get(
        @Parameter(description = "The id of the entity to be retrieved.", required = true)
        @PathParam("id") String id);
}

public class EntityResponse {
    @JsonProperty("id")
    String id;

    @JsonProperty("name")
    String name;

    @JsonProperty("pets")
    List<? extends PetResponse> pets;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({
    @Type(value = DogResponse.class, name = "DOG"),
    @Type(value = CatResponse.class, name = "CAT"),
})
@Schema(discriminatorProperty = "type", discriminatorMapping = {
    @DiscriminatorMapping(value = "DOG", schema = DogResponse.class),
    @DiscriminatorMapping(value = "CAT", schema = CatResponse.class)
}, anyOf = {DogResponse.class, CatResponse.class})
public abstract class PetResponse {
    protected String id;
    protected PetType type;

    @JsonProperty("id")
    public String getId() {
        return id;
    }

    @JsonProperty("type")
    public PetType getType() {
        return type;
    }
}

public class DogResponse extends PetResponse {
    String weight;

    @JsonProperty("weight")
    public String getWeight() {
        return weight;
    }
}

public class CatResponse extends PetResponse {
    String color;

    @JsonProperty("color")
    public String getColor() {
        return color;
    }
}

generates the following OpenAPI description:

"/test_polymorphic/{id}" : {
      "get" : {
        "tags" : [ "/test_polymorphic" ],
        "summary" : "Get the entity by id",
        "operationId" : "get_1",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "description" : "The id of the entity to be retrieved.",
          "required" : true,
          "schema" : {
            "type" : "string"
          }
        } ],
        "responses" : {
          "default" : {
            "description" : "default response",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/EntityResponse"
                }
              }
            }
          }
        }
      }
    }

  "EntityResponse" : {
    "type" : "object",
    "properties" : {
      "id" : {
        "type" : "string"
      },
      "name" : {
        "type" : "string"
      },
      "pets" : {
        "type" : "array",
        "items" : {
          "$ref" : "#/components/schemas/PetResponse"
        }
      }
    }
  },
  "PetResponse" : {
    "type" : "object",
    "properties" : {
      "id" : {
        "type" : "string"
      },
      "type" : {
        "type" : "string",
        "enum" : [ "DOG", "CAT" ]
      }
    },
    "discriminator" : {
      "propertyName" : "type",
      "mapping" : {
        "DOG" : "#/components/schemas/DogResponse",
        "CAT" : "#/components/schemas/CatResponse"
      }
    },
    "anyOf" : [ {
      "$ref" : "#/components/schemas/DogResponse"
    }, {
      "$ref" : "#/components/schemas/CatResponse"
    } ]
  },

  "CatResponse" : {
    "type" : "object",
    "allOf" : [ {
      "$ref" : "#/components/schemas/PetResponse"
    }, {
      "type" : "object",
      "properties" : {
        "color" : {
          "type" : "string"
        }
      }
    } ]
  },
  "DogResponse" : {
    "type" : "object",
    "allOf" : [ {
      "$ref" : "#/components/schemas/PetResponse"
    }, {
      "type" : "object",
      "properties" : {
        "weight" : {
          "type" : "string"
        }
      }
    } ]
  }

As you can see the pets polymorphic array is generated as:

"pets" : {
    "type" : "array",
    "items" : {
      "$ref" : "#/components/schemas/PetResponse"
    }
}

And the parent schema PetResponse has the anyOf defined on it. This results in the swagger-ui to show it wrongly. See the attached screenshots. The example doesn't display the parent fields. The schema shows the parent fields and then says that there will be anyOf subclasses. wrong-example wrong-schema

I found this ticket on the OpenAPI https://github.com/OAI/OpenAPI-Specification/issues/1627 which says that the polymorphic arrays should be represented differently. After manually changing the pets polymorphic array definition to:

"pets" : {
    "type" : "array",
    "items" : {
      "anyOf" : [ {
        "$ref" : "#/components/schemas/DogResponse"
      }, {
        "$ref" : "#/components/schemas/CatResponse"
      } ]
    }
}

the swagger-ui started to represent it correctly (I also had to remove the anyOf from the @Schema annotation on the PetResponse) correct-example correct-schema

I assume that there is a bug in the ModelResolver for defining polymorphic arrays. Please advice. I also found this TODO comment in the ModelResolver on line 387: Screen Shot 2021-01-22 at 19 34 08

I'm using the latest swagger-core and swagger-jaxrs2 (version 2.1.6)

azagorneanu avatar Jan 26 '21 15:01 azagorneanu