springdoc-openapi spec generation for inheritance witth generics - swagger

I have a Spring Boot (kotlin) project for which I use springdoc-openapi to generate OpenApi 3 spec. My data model looks like so:
open class Animal
data class Cat(val catName: String) : Animal()
data class Dog(val dogName: String) : Animal()
open class Food<T : Animal>
class CatFood : Food<Cat>()
class DogFood : Food<Dog>()
and a simple controller like so:
#GetMapping("/test")
fun test(): Food<out Animal> = DogFood()
for which the generated yaml is:
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8085
paths:
/test:
get:
tags:
- test-controller
operationId: test
responses:
"200":
description: default response
content:
'*/*':
schema:
$ref: '#/components/schemas/FoodAnimal'
components:
schemas:
FoodAnimal:
type: object
The problem here is that my controller can return either DogFood or CatFood, and that is specified in the return type. The schema I would expect to be generated is:
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8085
paths:
/test:
get:
tags:
- test-controller
operationId: test
responses:
"200":
description: default response
content:
'*/*':
schema:
oneOf:
- $ref: '#/components/schemas/FoodAnimal'
- $ref: '#/components/schemas/DogFood'
- $ref: '#/components/schemas/CatFood'
components:
schemas:
FoodAnimal:
type: object
CatFood:
allOf:
- $ref: '#/components/schemas/FoodAnimal'
type: object
DogFood:
allOf:
- $ref: '#/components/schemas/FoodAnimal'
type: object
Is there some way to achieve this?

For inheritance, you just need to add #Schema annotation, on your parent class:
#Schema(
type = "object",
title = "Food",
subTypes = [CatFood::class, DogFood::class]
)
open class Food<T : Animal>
class CatFood : Food<Cat>()
class DogFood : Food<Dog>()
If you need a reponse using oneOf, you will have to add #Response:
#GetMapping("/test")
#ApiResponse(content = [Content(mediaType = "*/*", schema = Schema(oneOf = [Food::class, CatFood::class,DogFood::class]))])
fun test(): Food<out Animal> = DogFood()

I had problems using OpenApi with inheritance for nested properties.
I used JsonSubtype annotations and generics as a workaround.
data class AnimalResponse<FoodResponse>(
val id: UUID,
val eats: FoodResponse
)
#JsonSubTypes(value = [
JsonSubTypes.Type(
value = CatFoodResponse::class,
name = "CAT_FOOD"
), JsonSubTypes.Type(
value = DogFoodResponse::class,
name = "DOG_FOOD"
)])
interface FoodResponse
This will show all types of FoodResponses in AnimalResponse Schema.

Related

How to define in swagger spec either of two properties are not null and required

I have a requirement to have two properties in the payload say property1 and propert2. Both are array type. Condition is, that either both can have values, or only one of them will be non-null. But both cannot be null at the same time.
How can I define this requirement in swagger spec, so that I can enforce schema such that both should not be null but any one of them can be null.
Valid Examples:
Ex1--> {"Property1": ["value1"], "Property2": ["value2"]}
Ex2--> {"Property2": ["value2"]}
Ex3--> {"Property1": ["value1"]}
Invalid Example:
{"Property1": [], "Property2": []}
Assuming the object can only contain these two properties (Property1 and/or Property2) and no other properties, your use case can be defined using a combination of object-level minProperties and array-level minItems:
MyObject:
type: object
minProperties: 1 # <-- requires at least one of Property1 or Property2 to be present
properties:
Property1:
type: array
items:
type: string
minItems: 1 # <-----
Property2:
type: array
items:
type: string
minItems: 1 # <-----
If the payload can also contain other properties, the "either Property1 or Property2 must be present" condition can be defined using anyOf + required. This requires OpenAPI 3.x:
# openapi: 3.x.x
MyObject:
type: object
anyOf: # <----------------
- required: [Property1]
- required: [Property2]
properties:
Property1:
type: array
items:
type: string
minItems: 1
Property2:
type: array
items:
type: string
minItems: 1
SomeOtherProperty:
...

How to make swagger examples component with allOf

components:
examples:
J:
value:
name: my name
J2:
value:
allOf:
- $ref: '#/components/examples/J'
- id: 123
Use J2 in response body examples:
responses:
200:
content:
application/json:
examples:
test:
$ref: '#/components/examples/J2'
Current result:
{
"$ref": "#/components/examples/J",
"id": 123
}
Expected result
{
"name": "my name",
"id": 123
}
Related:
https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
Swagger schema properties ignored when using $ref - why?
This is not supported.
The value is meant to be the literal value of the example. As a result, value does not support allOf and $ref. OpenAPI does not have a way to merge the values of multiple example components.
You cannot combine at example level but you can combine examples at path level.
This is invalid
components:
examples:
J:
value:
name: my name
J2:
value:
allOf:
- $ref: '#/components/examples/J'
- id: 123
But you can do this
components:
examples:
J:
value:
name: my name
J2:
value:
id: 123
content:
application/json:
schema:
$ref: '#/components/schemas/MyObject'
examples:
objectExample:
allOf :
- $ref: '#/components/examples/J'
- $ref: '#/components/examples/J2'

OpenAPI 3: Use array of objects for requestBody example

How can I define workingDays as array so that dayIndex, dayStart and dayEnd are one object inside?
* /business-calendar/:
* post:
* description: Add a new business calender
* responses:
* '200':
* description: Business calendar added
* requestBody:
* content:
* application/json:
* schema:
* type: object
* example:
* name: "Standard"
* validFrom: "2021-01-01T00:00:00.000Z"
* validTo: "2021-12-31T00:00:00.000Z"
* useHolidays: true
* workingDays:
* dayIndex: 0
* dayStart: "8:00"
* dayEnd: "20:00"
*/
Current output:
{
"name": "Standard",
"validFrom": "2021-01-01T00:00:00.000Z",
"validTo": "2021-12-31T00:00:00.000Z",
"useHolidays": true,
"workingDays": {
"dayIndex": 0,
"dayStart": "8:00",
"dayEnd": "20:00"
}
}
Desired output:
{
"name": "Standard",
"validFrom": "2021-01-01T00:00:00.000Z",
"validTo": "2021-12-31T00:00:00.000Z",
"useHolidays": true,
"workingDays": [
{
"dayIndex": 0,
"dayStart": "8:00",
"dayEnd": "20:00"
}
]
}
The way I would approach this would be to create a workingDay object, and pass an array of those objects in the request body. To do this, you'll want workingDay to be defined as a schema with the desired properties in the schema section under components. Notice the use of '$ref' to point an example workingDay object:
https://swagger.io/docs/specification/using-ref/
workingDay:
description: describe my working days
type: object
example:
$ref: "#/components/examples/workingDay"
properties:
dayIndex:
description: index my day
type: foo
dayStart:
description: start my day
type: bar
dayEnd:
description: end my day
type: baz
The example object might look something like this:
https://swagger.io/docs/specification/adding-examples/
workingDay:
summary: example values of a workingDay object
value:
dayIndex: 0
dayStart: 8:00
dayEnd: 20:00
Then when you go to describe the endpoint requestBody, it'll look more like this:
https://swagger.io/docs/specification/describing-request-body/
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
validFrom:
type: string
validTo:
type: string
useHolidays:
type: bool
workingDays:
type: array
items:
$ref: "#/components/schemas/workingDay"

Error "bad indentation of a mapping entry" in Swagger editor

In the OpenAPI definition below, the parameter definition causes the parser error "bad indentation of a mapping entry". What is wrong? Error at "properties" (last 5th line)
responses:
'200':
description: OK
content:
application/json;charset=UTF-8:
schema:
type: object
properties:
creationTime:
type: string
format: date-time
description: >-
The date and time the response was created in GMT time
with a format of "MM-dd-yyyy HH:mm:ss.SSSZ"
example: '03-12-2019 14:05:35.182-0500'
response:
type: array
description: ''
properties:
recordID:
type: string
maxLength: 20
externalID:
The error occurs because the properties keyword is indented more than its sibling type and description keywords.
response:
type: array
description: ''
properties: # <---- Indentation doesn't match the sibling
# `type` and `description` keywords
However, type: array schemas need items, not properties.
It looks like the response key is either missing some keywords or has extra keywords. The correct definition for response would be:
response:
type: object # <----------
description: ''
properties: # <----------
recordID:
type: string
maxLength: 20
externalID:
...
or
response:
type: array
description: ''
items: # <----------
type: object # <----------
properties: # <----------
recordID:
type: string
maxLength: 20
externalID:
...
depending on what you need.

Model composition in swagger spec without additional nesting layer

If I have the following example where Settings definition is embedded in Thing via composition:
definitions:
Settings:
properties:
foobar:
type: number
format: double
boofar:
type: string
Thing:
properties:
allOf:
$ref: '#/definitions/Settings'
name:
type: string
If I define a method to POST a Thing in editor.swagger.io, it ends up constructing JSON that looks like this:
{
settings: {
foobar: 1,
boofar: "text here"
},
name: "some name"
}
I want to embed a model definition with composition but without the additional nested property definition -- is this possible? This is the JSON structure I would like to have for Thing:
{
foobar: 1,
boofar: "text here",
name: "some name"
}
Is there a way to achieve this?
Your example do not really use composition as allOf is a property.
allOf is supposed to be on the root of the definition and it's an array of schema (reference or inline).
Here's the proper way to use allOf for your example:
swagger: '2.0'
info:
title: API
version: 1.0.0
paths:
/thing:
get:
responses:
200:
description: OK
schema:
$ref: '#/definitions/Thing'
definitions:
Settings:
properties:
foobar:
type: number
format: double
boofar:
type: string
Thing:
allOf:
- $ref: '#/definitions/Settings'
- properties:
name:
type: string
Resulting rendering in SwaggerUI:

Resources