cancel
Showing results for 
Search instead for 
Did you mean: 

Using inheritance and having both base type and subtypes in endpoints breaks the object hierarchy

Visitor

Using inheritance and having both base type and subtypes in endpoints breaks the object hierarchy

Hi all, I'm using swagger 2.1.0 to annotate my JAX-RS ressources and to generate the openAPI documentation. I'm facing a problem with inheritance. In order to expose the situation we can take the Pet example from the openAPI specification. I implemented it as follows in java with jackson annotations:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({ 
	@Type(value = Cat.class), 
	@Type(value = Dog.class) 
})
public class Pet {

	private String name;

	public final String getName() {
		return name;
	}
	public final void setName(String name) {
		this.name = name;
	}
}
public class Cat extends Pet {

	public enum HuntingSkill { clueless, lazy, adventurous, aggressive};
	
	private HuntingSkill huntingSkill;
	
	public final HuntingSkill getHuntingSkill() {
		return huntingSkill;
	}
	public final void setHuntingSkill(HuntingSkill huntingSkill) {
		this.huntingSkill = huntingSkill;
	}
}
public class Dog extends Pet {

	private int packSize;
	
	public final int getPackSize() {
		return packSize;
	}
	public final void setPackSize(int packSize) {
		this.packSize = packSize;
	}
}

and I define the following two Endpoints in a JAX-RS resource:

@Path("/")
public final class SwaggerTestRS {
	
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/pet/{name}")
	@Operation(
		summary = "Gets the pet having the given name", 
		responses = @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = Pet.class)))
	)
	public Response getPet(
			@PathParam("name")
			String name) {
		return Response.ok().build();
	}
	
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/pet/cat/{name}")
	@Operation(
		summary = "Gets the cat having the given name", 
		responses = @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = Cat.class)))
	)
	public Response getCat(
			@PathParam("name")
			String name) {
		return Response.ok().build();
	}
}

The endpoint getPet() is defined to return a Pet and getCat() a Cat. Out of this api the following openAPI documentation gets generated:

openapi: 3.0.1
paths:
  /pet/{name}:
    get:
      summary: Gets the pet having the given name
      operationId: getPet
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
  /pet/cat/{name}:
    get:
      summary: Gets the cat having the given name
      operationId: getCat
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cat'
components:
  schemas:
    Cat:
      type: object
      properties:
        name:
          type: string
        huntingSkill:
          type: string
          enum:
          - clueless
          - lazy
          - adventurous
          - aggressive
    Dog:
      type: object
      allOf:
      - $ref: '#/components/schemas/Pet'
      - type: object
        properties:
          packSize:
            type: integer
            format: int32
    Pet:
      required:
      - type
      type: object
      properties:
        name:
          type: string
        type:
          type: string
      discriminator:
        propertyName: 

Dog does inherit from Pet with the "allOf" definition, cat on the other hand does not. So Cat is no more a Pet and is no more an acceptable return type for the getPet() endpoint.

By removing the getCat() endpoint, the openApi documentation is generated as I would expect, with both Dog and Cat "extending" Pet.

Is this the expected behaviour or might it be a bug?

Thank you very much for your thoughs and maybe workarounds!