I'm using an OpenApi generator setup on Maven + Spring Boot (2.7.7) to generate API interfaces to implement in my software. This includes Validation.
I'm trying to understand if there is a way to specify validation in an OpenApi (3.0.1 atm but I'm flexible) yaml in such a way that I can have an object not be mandatory, but if ANY fields are included, then all of it must be included.
For example:
paths:
/api/complexobject:
get:
tags:
- complexobject
summary: generic search
operationId: findAll
parameters:
- in: query
name: complexobject
schema:
$ref: '#/components/schemas/ComplexObject'
explode: true
- in: query
name: sort
schema:
$ref: '#/components/schemas/Sorting'
- in: query
name: page
schema:
$ref: '#/components/schemas/Pagination'
responses:
"200":
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ComplexObject'
components:
schemas:
ComplexObject:
type: object
<cut>
Pagination:
type: object
properties:
page:
type: integer
minimum: 0
description: 0-indexed page number for pagination
size:
type: integer
minimum: 1
description: Number of returned records (page size). Suggested default is 20
required:
- page
- size
Sorting:
type: object
properties:
dir:
$ref: '#/components/schemas/SortDir'
sorted:
type: array
items:
type: string
required:
- dir
- sorted
SortDir:
type: string
enum: ["asc", "desc"]
The intention here is that the Sorting and the Pagination objects can be omitted from the query, however IF they're included, they must be wholly included (and with valid inputs too).
However it seems that at runtime the Pagination and Sorting Java Objects are instanced even without any parameters being sent to the Controller, which makes them fail the validation.
Is there some different validation setup I can use in the yaml, or do I need to change something in Java code?
Or, just remove the requirements altogether from the OpenAPI field declation and do programmatic validation directly instead?
As far as I can tell the specification I wrote is correct, however the generator (the most recent one available at the time of writing) doesn't support this kind of behaviour.
However what it does support is the use of defaults:
Pagination:
type: object
nullable: true
properties:
page:
type: integer
minimum: 0
default: 0 # Added default
description: 0-indexed page number for pagination
size:
type: integer
minimum: 1
default: 20 # Added default
description: Number of returned records (page size).
required:
- page
- size
Sorting:
type: object
nullable: true
properties:
dir:
$ref: '#/components/schemas/SortDir'
sorted:
type: array # No default needed, see below!
items:
type: string
required:
- dir
- sorted
SortDir:
type: string
default: "asc" # Added default
enum: ["asc", "desc"]
In this case you'll actually be bypassing the problem a bit, in that you'll never end up in a situation when any of the required fields will ever be null, see the generated code for reference:
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-01-30T16:25:58.375+01:00[Europe/Berlin]")
public class Pagination {
@JsonProperty("page")
private Integer page = 0;
@JsonProperty("size")
private Integer size = 20;
As a side note, the validation for the "sorted" property actually works as expected, even though the way the codegen attains that is weird. It'll work the same way whether you specify it as required or not AND without specifying defaults.
However we have the two following codegens:
Without required:
@JsonProperty("sorted")
@Valid
private List<String> sorted = null;
// Cut for brevity
/**
* Get sorted
* @return sorted
*/
@NotNull
@Schema(name = "sorted", required = false)
public List<String> getSorted() {
return sorted;
}
WITH required:
@JsonProperty("sorted")
@Valid
private List<String> sorted = new ArrayList<>();
// Cut for brevity
/**
* Get sorted
* @return sorted
*/
@NotNull
@Schema(name = "sorted", required = true)
public List<String> getSorted() {
return sorted;
}
This also means that the validation in the second case WILL be active... but never fail.